Merge pull request #6847 from hashicorp/pcf-auto-auth
Add PCF auth method, agent, and CLI handler
This commit is contained in:
commit
b965ce035c
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/hashicorp/vault/command/agent/auth/gcp"
|
||||
"github.com/hashicorp/vault/command/agent/auth/jwt"
|
||||
"github.com/hashicorp/vault/command/agent/auth/kubernetes"
|
||||
"github.com/hashicorp/vault/command/agent/auth/pcf"
|
||||
"github.com/hashicorp/vault/command/agent/cache"
|
||||
"github.com/hashicorp/vault/command/agent/config"
|
||||
"github.com/hashicorp/vault/command/agent/sink"
|
||||
|
@ -342,6 +343,8 @@ func (c *AgentCommand) Run(args []string) int {
|
|||
method, err = kubernetes.NewKubernetesAuthMethod(authConfig)
|
||||
case "approle":
|
||||
method, err = approle.NewApproleAuthMethod(authConfig)
|
||||
case "pcf":
|
||||
method, err = pcf.NewPCFAuthMethod(authConfig)
|
||||
default:
|
||||
c.UI.Error(fmt.Sprintf("Unknown auth method %q", config.AutoAuth.Method.Type))
|
||||
return 1
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package pcf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
pcf "github.com/hashicorp/vault-plugin-auth-pcf"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/signatures"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/command/agent/auth"
|
||||
)
|
||||
|
||||
type pcfMethod struct {
|
||||
mountPath string
|
||||
roleName string
|
||||
}
|
||||
|
||||
func NewPCFAuthMethod(conf *auth.AuthConfig) (auth.AuthMethod, error) {
|
||||
if conf == nil {
|
||||
return nil, errors.New("empty config")
|
||||
}
|
||||
if conf.Config == nil {
|
||||
return nil, errors.New("empty config data")
|
||||
}
|
||||
a := &pcfMethod{
|
||||
mountPath: conf.MountPath,
|
||||
}
|
||||
if raw, ok := conf.Config["role"]; ok {
|
||||
if roleName, ok := raw.(string); ok {
|
||||
a.roleName = roleName
|
||||
} else {
|
||||
return nil, errors.New("could not convert 'role' config value to string")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("missing 'role' value")
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (p *pcfMethod) Authenticate(ctx context.Context, client *api.Client) (string, map[string]interface{}, error) {
|
||||
pathToClientCert := os.Getenv(pcf.EnvVarInstanceCertificate)
|
||||
if pathToClientCert == "" {
|
||||
return "", nil, fmt.Errorf("missing %q value", pcf.EnvVarInstanceCertificate)
|
||||
}
|
||||
certBytes, err := ioutil.ReadFile(pathToClientCert)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pathToClientKey := os.Getenv(pcf.EnvVarInstanceKey)
|
||||
if pathToClientKey == "" {
|
||||
return "", nil, fmt.Errorf("missing %q value", pcf.EnvVarInstanceKey)
|
||||
}
|
||||
signingTime := time.Now().UTC()
|
||||
signatureData := &signatures.SignatureData{
|
||||
SigningTime: signingTime,
|
||||
Role: p.roleName,
|
||||
CFInstanceCertContents: string(certBytes),
|
||||
}
|
||||
signature, err := signatures.Sign(pathToClientKey, signatureData)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"role": p.roleName,
|
||||
"cf_instance_cert": string(certBytes),
|
||||
"signing_time": signingTime.Format(signatures.TimeFormat),
|
||||
"signature": signature,
|
||||
}
|
||||
return fmt.Sprintf("%s/login", p.mountPath), data, nil
|
||||
}
|
||||
|
||||
func (p *pcfMethod) NewCreds() chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pcfMethod) CredSuccess() {}
|
||||
|
||||
func (p *pcfMethod) Shutdown() {}
|
|
@ -0,0 +1,170 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
credPCF "github.com/hashicorp/vault-plugin-auth-pcf"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates"
|
||||
pcfAPI "github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/command/agent/auth"
|
||||
agentpcf "github.com/hashicorp/vault/command/agent/auth/pcf"
|
||||
"github.com/hashicorp/vault/command/agent/sink"
|
||||
"github.com/hashicorp/vault/command/agent/sink/file"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestPCFEndToEnd(t *testing.T) {
|
||||
logger := logging.NewVaultLogger(hclog.Trace)
|
||||
|
||||
coreConfig := &vault.CoreConfig{
|
||||
DisableMlock: true,
|
||||
DisableCache: true,
|
||||
Logger: log.NewNullLogger(),
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"pcf": credPCF.Factory,
|
||||
},
|
||||
}
|
||||
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
cores := cluster.Cores
|
||||
vault.TestWaitActive(t, cores[0].Core)
|
||||
client := cores[0].Client
|
||||
if err := client.Sys().EnableAuthWithOptions("pcf", &api.EnableAuthOptions{
|
||||
Type: "pcf",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testIPAddress := "127.0.0.1"
|
||||
|
||||
// Generate some valid certs that look like the ones we get from PCF.
|
||||
testPCFCerts, err := certificates.Generate(pcfAPI.FoundServiceGUID, pcfAPI.FoundOrgGUID, pcfAPI.FoundSpaceGUID, pcfAPI.FoundAppGUID, testIPAddress)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := testPCFCerts.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start a mock server representing their API.
|
||||
mockPCFAPI := pcfAPI.MockServer(false)
|
||||
defer mockPCFAPI.Close()
|
||||
|
||||
// Configure a CA certificate like a Vault operator would in setting up PCF.
|
||||
if _, err := client.Logical().Write("auth/pcf/config", map[string]interface{}{
|
||||
"identity_ca_certificates": testPCFCerts.CACertificate,
|
||||
"pcf_api_addr": mockPCFAPI.URL,
|
||||
"pcf_username": pcfAPI.AuthUsername,
|
||||
"pcf_password": pcfAPI.AuthPassword,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Configure a role to be used for logging in, another thing a Vault operator would do.
|
||||
if _, err := client.Logical().Write("auth/pcf/roles/test-role", map[string]interface{}{
|
||||
"bound_instance_ids": pcfAPI.FoundServiceGUID,
|
||||
"bound_organization_ids": pcfAPI.FoundOrgGUID,
|
||||
"bound_space_ids": pcfAPI.FoundSpaceGUID,
|
||||
"bound_application_ids": pcfAPI.FoundAppGUID,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv(credPCF.EnvVarInstanceCertificate, testPCFCerts.PathToInstanceCertificate)
|
||||
os.Setenv(credPCF.EnvVarInstanceKey, testPCFCerts.PathToInstanceKey)
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
timer := time.AfterFunc(30*time.Second, func() {
|
||||
cancelFunc()
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
am, err := agentpcf.NewPCFAuthMethod(&auth.AuthConfig{
|
||||
MountPath: "auth/pcf",
|
||||
Config: map[string]interface{}{
|
||||
"role": "test-role",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ahConfig := &auth.AuthHandlerConfig{
|
||||
Logger: logger.Named("auth.handler"),
|
||||
Client: client,
|
||||
}
|
||||
|
||||
ah := auth.NewAuthHandler(ahConfig)
|
||||
go ah.Run(ctx, am)
|
||||
defer func() {
|
||||
<-ah.DoneCh
|
||||
}()
|
||||
|
||||
tmpFile, err := ioutil.TempFile("", "auth.tokensink.test.")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tokenSinkFileName := tmpFile.Name()
|
||||
tmpFile.Close()
|
||||
os.Remove(tokenSinkFileName)
|
||||
t.Logf("output: %s", tokenSinkFileName)
|
||||
|
||||
config := &sink.SinkConfig{
|
||||
Logger: logger.Named("sink.file"),
|
||||
Config: map[string]interface{}{
|
||||
"path": tokenSinkFileName,
|
||||
},
|
||||
WrapTTL: 10 * time.Second,
|
||||
}
|
||||
|
||||
fs, err := file.NewFileSink(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.Sink = fs
|
||||
|
||||
ss := sink.NewSinkServer(&sink.SinkServerConfig{
|
||||
Logger: logger.Named("sink.server"),
|
||||
Client: client,
|
||||
})
|
||||
go ss.Run(ctx, ah.OutputCh, []*sink.SinkConfig{config})
|
||||
defer func() {
|
||||
<-ss.DoneCh
|
||||
}()
|
||||
|
||||
if stat, err := os.Lstat(tokenSinkFileName); err == nil {
|
||||
t.Fatalf("expected err but got %s", stat)
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Fatal("expected notexist err")
|
||||
}
|
||||
|
||||
// Wait 2 seconds for the env variables to be detected and an auth to be generated.
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
token, err := readToken(tokenSinkFileName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if token.Token == "" {
|
||||
t.Fatal("expected token but didn't receive it")
|
||||
}
|
||||
}
|
|
@ -355,6 +355,7 @@ func TestPredict_Plugins(t *testing.T) {
|
|||
"nomad",
|
||||
"oidc",
|
||||
"okta",
|
||||
"pcf",
|
||||
"pki",
|
||||
"postgresql",
|
||||
"postgresql-database-plugin",
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
credCentrify "github.com/hashicorp/vault-plugin-auth-centrify"
|
||||
credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin"
|
||||
credOIDC "github.com/hashicorp/vault-plugin-auth-jwt"
|
||||
credPCF "github.com/hashicorp/vault-plugin-auth-pcf"
|
||||
credAws "github.com/hashicorp/vault/builtin/credential/aws"
|
||||
credCert "github.com/hashicorp/vault/builtin/credential/cert"
|
||||
credGitHub "github.com/hashicorp/vault/builtin/credential/github"
|
||||
|
@ -162,6 +163,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
|||
"ldap": &credLdap.CLIHandler{},
|
||||
"oidc": &credOIDC.CLIHandler{},
|
||||
"okta": &credOkta.CLIHandler{},
|
||||
"pcf": &credPCF.CLIHandler{},
|
||||
"radius": &credUserpass.CLIHandler{
|
||||
DefaultMount: "radius",
|
||||
},
|
||||
|
|
3
go.mod
3
go.mod
|
@ -58,7 +58,7 @@ require (
|
|||
github.com/hashicorp/errwrap v1.0.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
||||
github.com/hashicorp/go-gcp-common v0.5.0
|
||||
github.com/hashicorp/go-hclog v0.8.0
|
||||
github.com/hashicorp/go-hclog v0.9.2
|
||||
github.com/hashicorp/go-memdb v1.0.0
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/hashicorp/go-rootcerts v1.0.0
|
||||
|
@ -74,6 +74,7 @@ require (
|
|||
github.com/hashicorp/vault-plugin-auth-gcp v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-jwt v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190508211750-4152192cdc0f
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.5.1
|
||||
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.1
|
||||
|
|
33
go.sum
33
go.sum
|
@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
|
@ -16,6 +18,8 @@ github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae
|
|||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E=
|
||||
github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
|
||||
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
|
@ -76,10 +80,13 @@ github.com/circonus-labs/circonus-gometrics v2.2.7+incompatible/go.mod h1:nmEj6D
|
|||
github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5LbakNExNmZIg=
|
||||
|
@ -139,6 +146,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||
github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
|
@ -154,6 +162,8 @@ github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
|||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
|
||||
|
@ -189,6 +199,7 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY=
|
||||
|
@ -224,6 +235,8 @@ github.com/hashicorp/go-gcp-common v0.5.0/go.mod h1:IDGUI2N/OS3PiU4qZcXJeWKPI6O/
|
|||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0 h1:z3ollgGRg8RjfJH6UVBaG54R70GFd++QOkvnJH3VSBY=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-memdb v1.0.0 h1:K1O4N2VPndZiTrdH3lmmf5bemr9Xw81KjVwhReIUjTQ=
|
||||
|
@ -232,6 +245,8 @@ github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqk
|
|||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v1.0.0 h1:/gQ1sNR8/LHpoxKRQq4PmLBuacfZb4tC93e9B30o/7c=
|
||||
github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE=
|
||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
|
||||
|
@ -274,8 +289,16 @@ github.com/hashicorp/vault-plugin-auth-jwt v0.5.1 h1:d9WLI7oF6VMtwBZwS5bbChc4kW+
|
|||
github.com/hashicorp/vault-plugin-auth-jwt v0.5.1/go.mod h1:5VU7gc6/BEEFQW/viqMs3LBxI1D1cxJmKqKQEP3JUP4=
|
||||
github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.1 h1:q6DGb12Vw/CpZ9xDWAmpzxVRKeClFqRFgbIZ3fZcvuY=
|
||||
github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.1/go.mod h1:qCDsm0njdfUrnN5sFKMLjxGjZKjQf2qB6dReQ4gr4YI=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190524170107-2d769dfedad4 h1:dSp8yiXmbfqKfS0j7//fTA+uw3G4OPt7COKDf7oYmMQ=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190524170107-2d769dfedad4/go.mod h1:9866PkjxPBXclbEJBKzVGY60pgVIY9b7qZJ5Fa+p6VY=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190605234735-619218abcd26 h1:mz5YaAFveImGMooFLAW14kdSBH4jVdRnKTQYAz0fEHw=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190605234735-619218abcd26/go.mod h1:9866PkjxPBXclbEJBKzVGY60pgVIY9b7qZJ5Fa+p6VY=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c h1:/g4Yr7pCTfKVqjUUVO4/Pkd3Vmw2TB3znuB4lF7ZNNY=
|
||||
github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c/go.mod h1:AjWJZO3nIHzc1inkx57x5qtIIcpi1sejXiwJVcNpjyc=
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190508211750-4152192cdc0f h1:BYQVawXauMXQ26I3Pn1Nw9kp/aZD60xmh9ZP3jum0YM=
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190508211750-4152192cdc0f/go.mod h1:CkOYWfeuC5nAzehBztl94S6VOn2g50h1tffpcNoWCZ8=
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190617182336-fe4c97e18808 h1:taTbXUW9En/vHp7tVdjhO5XLUmHYxuFJZar+35H7PPg=
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190617182336-fe4c97e18808/go.mod h1:CkOYWfeuC5nAzehBztl94S6VOn2g50h1tffpcNoWCZ8=
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.5.1 h1:BdiASUZLOvOUs317EnaUNjGxTSw0PYGQA7zJZhDKLC4=
|
||||
github.com/hashicorp/vault-plugin-secrets-ad v0.5.1/go.mod h1:EH9CI8+0aWRBz8eIgGth0QjttmHWlGvn+8ZmX/ZUetE=
|
||||
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.1 h1:72K91p4uLhT/jgtBq2zV5Wn8ocvny4sAN56XOcTxK1w=
|
||||
|
@ -317,11 +340,13 @@ github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBv
|
|||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f h1:Gsc9mVHLRqBjMgdQCghN9NObCcRncDqxJvBvEaIIQEo=
|
||||
github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -332,11 +357,14 @@ github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
@ -399,6 +427,7 @@ github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm
|
|||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8=
|
||||
github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
|
@ -458,8 +487,10 @@ github.com/sirupsen/logrus v1.0.5 h1:8c8b5uO0zS4X6RPl/sd1ENwSkIc0/H2PaHxE3udaE8I
|
|||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
|
@ -534,6 +565,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2eP
|
|||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
|
||||
|
@ -570,6 +602,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin"
|
||||
credJWT "github.com/hashicorp/vault-plugin-auth-jwt"
|
||||
credKube "github.com/hashicorp/vault-plugin-auth-kubernetes"
|
||||
credPCF "github.com/hashicorp/vault-plugin-auth-pcf"
|
||||
credAppId "github.com/hashicorp/vault/builtin/credential/app-id"
|
||||
credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
|
||||
credAws "github.com/hashicorp/vault/builtin/credential/aws"
|
||||
|
@ -76,6 +77,7 @@ func newRegistry() *registry {
|
|||
"ldap": credLdap.Factory,
|
||||
"oidc": credJWT.Factory,
|
||||
"okta": credOkta.Factory,
|
||||
"pcf": credPCF.Factory,
|
||||
"radius": credRadius.Factory,
|
||||
"userpass": credUserpass.Factory,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -0,0 +1,11 @@
|
|||
Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved.
|
||||
|
||||
This project contains software that is Copyright (c) 2014-2015 Pivotal Software, Inc.
|
||||
|
||||
This project is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
|
||||
You may not use this project except in compliance with the License.
|
||||
|
||||
This project may include a number of subcomponents with separate copyright notices
|
||||
and license terms. Your use of these subcomponents is subject to the terms and
|
||||
conditions of the subcomponent's license, as noted in the LICENSE file.
|
|
@ -0,0 +1,20 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func IsDirEmpty(dir string) (isEmpty bool, err error) {
|
||||
dirFile, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, readErr := dirFile.Readdirnames(1)
|
||||
if readErr != nil {
|
||||
isEmpty = true
|
||||
} else {
|
||||
isEmpty = false
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Open(path string) (file *os.File, err error) {
|
||||
err = os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
}
|
||||
|
||||
func Create(path string) (file *os.File, err error) {
|
||||
err = os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return os.Create(path)
|
||||
}
|
||||
|
||||
func CopyPathToPath(fromPath, toPath string) (err error) {
|
||||
srcFileInfo, err := os.Stat(fromPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if srcFileInfo.IsDir() {
|
||||
err = os.MkdirAll(toPath, srcFileInfo.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(fromPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err = CopyPathToPath(path.Join(fromPath, file.Name()), path.Join(toPath, file.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var dst *os.File
|
||||
dst, err = Create(toPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
dst.Chmod(srcFileInfo.Mode())
|
||||
|
||||
src, err := os.Open(fromPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func TempDir(namePrefix string, cb func(tmpDir string, err error)) {
|
||||
tmpDir, err := ioutil.TempDir("", namePrefix)
|
||||
|
||||
defer func() {
|
||||
os.RemoveAll(tmpDir)
|
||||
}()
|
||||
|
||||
cb(tmpDir, err)
|
||||
}
|
||||
|
||||
func TempFile(namePrefix string, cb func(tmpFile *os.File, err error)) {
|
||||
tmpFile, err := ioutil.TempFile("", namePrefix)
|
||||
|
||||
defer func() {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFile.Name())
|
||||
}()
|
||||
|
||||
cb(tmpFile, err)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- tip
|
||||
|
||||
# Setting sudo access to false will let Travis CI use containers rather than
|
||||
# VMs to run the tests. For more details see:
|
||||
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
|
||||
sudo: false
|
||||
|
||||
script:
|
||||
- make setup
|
||||
- make test
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: never # options: [always|never|change] default: always
|
|
@ -0,0 +1,86 @@
|
|||
# 1.4.2 (2018-04-10)
|
||||
|
||||
## Changed
|
||||
- #72: Updated the docs to point to vert for a console appliaction
|
||||
- #71: Update the docs on pre-release comparator handling
|
||||
|
||||
## Fixed
|
||||
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
|
||||
|
||||
# 1.4.1 (2018-04-02)
|
||||
|
||||
## Fixed
|
||||
- Fixed #64: Fix pre-release precedence issue (thanks @uudashr)
|
||||
|
||||
# 1.4.0 (2017-10-04)
|
||||
|
||||
## Changed
|
||||
- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill)
|
||||
|
||||
# 1.3.1 (2017-07-10)
|
||||
|
||||
## Fixed
|
||||
- Fixed #57: number comparisons in prerelease sometimes inaccurate
|
||||
|
||||
# 1.3.0 (2017-05-02)
|
||||
|
||||
## Added
|
||||
- #45: Added json (un)marshaling support (thanks @mh-cbon)
|
||||
- Stability marker. See https://masterminds.github.io/stability/
|
||||
|
||||
## Fixed
|
||||
- #51: Fix handling of single digit tilde constraint (thanks @dgodd)
|
||||
|
||||
## Changed
|
||||
- #55: The godoc icon moved from png to svg
|
||||
|
||||
# 1.2.3 (2017-04-03)
|
||||
|
||||
## Fixed
|
||||
- #46: Fixed 0.x.x and 0.0.x in constraints being treated as *
|
||||
|
||||
# Release 1.2.2 (2016-12-13)
|
||||
|
||||
## Fixed
|
||||
- #34: Fixed issue where hyphen range was not working with pre-release parsing.
|
||||
|
||||
# Release 1.2.1 (2016-11-28)
|
||||
|
||||
## Fixed
|
||||
- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha"
|
||||
properly.
|
||||
|
||||
# Release 1.2.0 (2016-11-04)
|
||||
|
||||
## Added
|
||||
- #20: Added MustParse function for versions (thanks @adamreese)
|
||||
- #15: Added increment methods on versions (thanks @mh-cbon)
|
||||
|
||||
## Fixed
|
||||
- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and
|
||||
might not satisfy the intended compatibility. The change here ignores pre-releases
|
||||
on constraint checks (e.g., ~ or ^) when a pre-release is not part of the
|
||||
constraint. For example, `^1.2.3` will ignore pre-releases while
|
||||
`^1.2.3-alpha` will include them.
|
||||
|
||||
# Release 1.1.1 (2016-06-30)
|
||||
|
||||
## Changed
|
||||
- Issue #9: Speed up version comparison performance (thanks @sdboyer)
|
||||
- Issue #8: Added benchmarks (thanks @sdboyer)
|
||||
- Updated Go Report Card URL to new location
|
||||
- Updated Readme to add code snippet formatting (thanks @mh-cbon)
|
||||
- Updating tagging to v[SemVer] structure for compatibility with other tools.
|
||||
|
||||
# Release 1.1.0 (2016-03-11)
|
||||
|
||||
- Issue #2: Implemented validation to provide reasons a versions failed a
|
||||
constraint.
|
||||
|
||||
# Release 1.0.1 (2015-12-31)
|
||||
|
||||
- Fixed #1: * constraint failing on valid versions.
|
||||
|
||||
# Release 1.0.0 (2015-10-20)
|
||||
|
||||
- Initial release
|
|
@ -0,0 +1,20 @@
|
|||
The Masterminds
|
||||
Copyright (C) 2014-2015, Matt Butcher and Matt Farina
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,36 @@
|
|||
.PHONY: setup
|
||||
setup:
|
||||
go get -u gopkg.in/alecthomas/gometalinter.v1
|
||||
gometalinter.v1 --install
|
||||
|
||||
.PHONY: test
|
||||
test: validate lint
|
||||
@echo "==> Running tests"
|
||||
go test -v
|
||||
|
||||
.PHONY: validate
|
||||
validate:
|
||||
@echo "==> Running static validations"
|
||||
@gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable deadcode \
|
||||
--severity deadcode:error \
|
||||
--enable gofmt \
|
||||
--enable gosimple \
|
||||
--enable ineffassign \
|
||||
--enable misspell \
|
||||
--enable vet \
|
||||
--tests \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || exit_code=1
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@echo "==> Running linters"
|
||||
@gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable golint \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || :
|
|
@ -0,0 +1,165 @@
|
|||
# SemVer
|
||||
|
||||
The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to:
|
||||
|
||||
* Parse semantic versions
|
||||
* Sort semantic versions
|
||||
* Check if a semantic version fits within a set of constraints
|
||||
* Optionally work with a `v` prefix
|
||||
|
||||
[![Stability:
|
||||
Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)
|
||||
[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.svg)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver)
|
||||
|
||||
## Parsing Semantic Versions
|
||||
|
||||
To parse a semantic version use the `NewVersion` function. For example,
|
||||
|
||||
```go
|
||||
v, err := semver.NewVersion("1.2.3-beta.1+build345")
|
||||
```
|
||||
|
||||
If there is an error the version wasn't parseable. The version object has methods
|
||||
to get the parts of the version, compare it to other versions, convert the
|
||||
version back into a string, and get the original string. For more details
|
||||
please see the [documentation](https://godoc.org/github.com/Masterminds/semver).
|
||||
|
||||
## Sorting Semantic Versions
|
||||
|
||||
A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/)
|
||||
package from the standard library. For example,
|
||||
|
||||
```go
|
||||
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||
vs := make([]*semver.Version, len(raw))
|
||||
for i, r := range raw {
|
||||
v, err := semver.NewVersion(r)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
vs[i] = v
|
||||
}
|
||||
|
||||
sort.Sort(semver.Collection(vs))
|
||||
```
|
||||
|
||||
## Checking Version Constraints
|
||||
|
||||
Checking a version against version constraints is one of the most featureful
|
||||
parts of the package.
|
||||
|
||||
```go
|
||||
c, err := semver.NewConstraint(">= 1.2.3")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, _ := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
// Check if the version meets the constraints. The a variable will be true.
|
||||
a := c.Check(v)
|
||||
```
|
||||
|
||||
## Basic Comparisons
|
||||
|
||||
There are two elements to the comparisons. First, a comparison string is a list
|
||||
of comma separated and comparisons. These are then separated by || separated or
|
||||
comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a
|
||||
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||
greater than or equal to 4.2.3.
|
||||
|
||||
The basic comparisons are:
|
||||
|
||||
* `=`: equal (aliased to no operator)
|
||||
* `!=`: not equal
|
||||
* `>`: greater than
|
||||
* `<`: less than
|
||||
* `>=`: greater than or equal to
|
||||
* `<=`: less than or equal to
|
||||
|
||||
_Note, according to the Semantic Version specification pre-releases may not be
|
||||
API compliant with their release counterpart. It says,_
|
||||
|
||||
> _A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version._
|
||||
|
||||
_SemVer comparisons without a pre-release value will skip pre-release versions.
|
||||
For example, `>1.2.3` will skip pre-releases when looking at a list of values
|
||||
while `>1.2.3-alpha.1` will evaluate pre-releases._
|
||||
|
||||
## Hyphen Range Comparisons
|
||||
|
||||
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||
These look like:
|
||||
|
||||
* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
|
||||
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5`
|
||||
|
||||
## Wildcards In Comparisons
|
||||
|
||||
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||
for all comparison operators. When used on the `=` operator it falls
|
||||
back to the pack level comparison (see tilde below). For example,
|
||||
|
||||
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||
* `<= 2.x` is equivalent to `<= 3`
|
||||
* `*` is equivalent to `>= 0.0.0`
|
||||
|
||||
## Tilde Range Comparisons (Patch)
|
||||
|
||||
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||
version is specified and major level changes when the minor number is missing.
|
||||
For example,
|
||||
|
||||
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
|
||||
* `~1` is equivalent to `>= 1, < 2`
|
||||
* `~2.3` is equivalent to `>= 2.3, < 2.4`
|
||||
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `~1.x` is equivalent to `>= 1, < 2`
|
||||
|
||||
## Caret Range Comparisons (Major)
|
||||
|
||||
The caret (`^`) comparison operator is for major level changes. This is useful
|
||||
when comparisons of API versions as a major change is API breaking. For example,
|
||||
|
||||
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||
* `^2.3` is equivalent to `>= 2.3, < 3`
|
||||
* `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||
|
||||
# Validation
|
||||
|
||||
In addition to testing a version against a constraint, a version can be validated
|
||||
against a constraint. When validation fails a slice of errors containing why a
|
||||
version didn't meet the constraint is returned. For example,
|
||||
|
||||
```go
|
||||
c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, _ := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
|
||||
// Validate a version against a constraint.
|
||||
a, msgs := c.Validate(v)
|
||||
// a is false
|
||||
for _, m := range msgs {
|
||||
fmt.Println(m)
|
||||
|
||||
// Loops over the errors which would read
|
||||
// "1.3 is greater than 1.2.3"
|
||||
// "1.3 is less than 1.4"
|
||||
}
|
||||
```
|
||||
|
||||
# Contribute
|
||||
|
||||
If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues)
|
||||
or [create a pull request](https://github.com/Masterminds/semver/pulls).
|
|
@ -0,0 +1,44 @@
|
|||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\Masterminds\semver
|
||||
shallow_clone: true
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
install:
|
||||
- go version
|
||||
- go env
|
||||
- go get -u gopkg.in/alecthomas/gometalinter.v1
|
||||
- set PATH=%PATH%;%GOPATH%\bin
|
||||
- gometalinter.v1.exe --install
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- "gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable deadcode \
|
||||
--severity deadcode:error \
|
||||
--enable gofmt \
|
||||
--enable gosimple \
|
||||
--enable ineffassign \
|
||||
--enable misspell \
|
||||
--enable vet \
|
||||
--tests \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || exit_code=1"
|
||||
- "gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable golint \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || :"
|
||||
- go test -v
|
||||
|
||||
deploy: off
|
|
@ -0,0 +1,24 @@
|
|||
package semver
|
||||
|
||||
// Collection is a collection of Version instances and implements the sort
|
||||
// interface. See the sort package for more details.
|
||||
// https://golang.org/pkg/sort/
|
||||
type Collection []*Version
|
||||
|
||||
// Len returns the length of a collection. The number of Version instances
|
||||
// on the slice.
|
||||
func (c Collection) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
// Less is needed for the sort interface to compare two Version objects on the
|
||||
// slice. If checks if one is less than the other.
|
||||
func (c Collection) Less(i, j int) bool {
|
||||
return c[i].LessThan(c[j])
|
||||
}
|
||||
|
||||
// Swap is needed for the sort interface to replace the Version objects
|
||||
// at two different positions in the slice.
|
||||
func (c Collection) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Constraints is one or more constraint that a semantic version can be
|
||||
// checked against.
|
||||
type Constraints struct {
|
||||
constraints [][]*constraint
|
||||
}
|
||||
|
||||
// NewConstraint returns a Constraints instance that a Version instance can
|
||||
// be checked against. If there is a parse error it will be returned.
|
||||
func NewConstraint(c string) (*Constraints, error) {
|
||||
|
||||
// Rewrite - ranges into a comparison operation.
|
||||
c = rewriteRange(c)
|
||||
|
||||
ors := strings.Split(c, "||")
|
||||
or := make([][]*constraint, len(ors))
|
||||
for k, v := range ors {
|
||||
cs := strings.Split(v, ",")
|
||||
result := make([]*constraint, len(cs))
|
||||
for i, s := range cs {
|
||||
pc, err := parseConstraint(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result[i] = pc
|
||||
}
|
||||
or[k] = result
|
||||
}
|
||||
|
||||
o := &Constraints{constraints: or}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Check tests if a version satisfies the constraints.
|
||||
func (cs Constraints) Check(v *Version) bool {
|
||||
// loop over the ORs and check the inner ANDs
|
||||
for _, o := range cs.constraints {
|
||||
joy := true
|
||||
for _, c := range o {
|
||||
if !c.check(v) {
|
||||
joy = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if joy {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate checks if a version satisfies a constraint. If not a slice of
|
||||
// reasons for the failure are returned in addition to a bool.
|
||||
func (cs Constraints) Validate(v *Version) (bool, []error) {
|
||||
// loop over the ORs and check the inner ANDs
|
||||
var e []error
|
||||
for _, o := range cs.constraints {
|
||||
joy := true
|
||||
for _, c := range o {
|
||||
if !c.check(v) {
|
||||
em := fmt.Errorf(c.msg, v, c.orig)
|
||||
e = append(e, em)
|
||||
joy = false
|
||||
}
|
||||
}
|
||||
|
||||
if joy {
|
||||
return true, []error{}
|
||||
}
|
||||
}
|
||||
|
||||
return false, e
|
||||
}
|
||||
|
||||
var constraintOps map[string]cfunc
|
||||
var constraintMsg map[string]string
|
||||
var constraintRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
constraintOps = map[string]cfunc{
|
||||
"": constraintTildeOrEqual,
|
||||
"=": constraintTildeOrEqual,
|
||||
"!=": constraintNotEqual,
|
||||
">": constraintGreaterThan,
|
||||
"<": constraintLessThan,
|
||||
">=": constraintGreaterThanEqual,
|
||||
"=>": constraintGreaterThanEqual,
|
||||
"<=": constraintLessThanEqual,
|
||||
"=<": constraintLessThanEqual,
|
||||
"~": constraintTilde,
|
||||
"~>": constraintTilde,
|
||||
"^": constraintCaret,
|
||||
}
|
||||
|
||||
constraintMsg = map[string]string{
|
||||
"": "%s is not equal to %s",
|
||||
"=": "%s is not equal to %s",
|
||||
"!=": "%s is equal to %s",
|
||||
">": "%s is less than or equal to %s",
|
||||
"<": "%s is greater than or equal to %s",
|
||||
">=": "%s is less than %s",
|
||||
"=>": "%s is less than %s",
|
||||
"<=": "%s is greater than %s",
|
||||
"=<": "%s is greater than %s",
|
||||
"~": "%s does not have same major and minor version as %s",
|
||||
"~>": "%s does not have same major and minor version as %s",
|
||||
"^": "%s does not have same major version as %s",
|
||||
}
|
||||
|
||||
ops := make([]string, 0, len(constraintOps))
|
||||
for k := range constraintOps {
|
||||
ops = append(ops, regexp.QuoteMeta(k))
|
||||
}
|
||||
|
||||
constraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`^\s*(%s)\s*(%s)\s*$`,
|
||||
strings.Join(ops, "|"),
|
||||
cvRegex))
|
||||
|
||||
constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`\s*(%s)\s+-\s+(%s)\s*`,
|
||||
cvRegex, cvRegex))
|
||||
}
|
||||
|
||||
// An individual constraint
|
||||
type constraint struct {
|
||||
// The callback function for the restraint. It performs the logic for
|
||||
// the constraint.
|
||||
function cfunc
|
||||
|
||||
msg string
|
||||
|
||||
// The version used in the constraint check. For example, if a constraint
|
||||
// is '<= 2.0.0' the con a version instance representing 2.0.0.
|
||||
con *Version
|
||||
|
||||
// The original parsed version (e.g., 4.x from != 4.x)
|
||||
orig string
|
||||
|
||||
// When an x is used as part of the version (e.g., 1.x)
|
||||
minorDirty bool
|
||||
dirty bool
|
||||
patchDirty bool
|
||||
}
|
||||
|
||||
// Check if a version meets the constraint
|
||||
func (c *constraint) check(v *Version) bool {
|
||||
return c.function(v, c)
|
||||
}
|
||||
|
||||
type cfunc func(v *Version, c *constraint) bool
|
||||
|
||||
func parseConstraint(c string) (*constraint, error) {
|
||||
m := constraintRegex.FindStringSubmatch(c)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("improper constraint: %s", c)
|
||||
}
|
||||
|
||||
ver := m[2]
|
||||
orig := ver
|
||||
minorDirty := false
|
||||
patchDirty := false
|
||||
dirty := false
|
||||
if isX(m[3]) {
|
||||
ver = "0.0.0"
|
||||
dirty = true
|
||||
} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
|
||||
minorDirty = true
|
||||
dirty = true
|
||||
ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
|
||||
} else if isX(strings.TrimPrefix(m[5], ".")) {
|
||||
dirty = true
|
||||
patchDirty = true
|
||||
ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
|
||||
}
|
||||
|
||||
con, err := NewVersion(ver)
|
||||
if err != nil {
|
||||
|
||||
// The constraintRegex should catch any regex parsing errors. So,
|
||||
// we should never get here.
|
||||
return nil, errors.New("constraint Parser Error")
|
||||
}
|
||||
|
||||
cs := &constraint{
|
||||
function: constraintOps[m[1]],
|
||||
msg: constraintMsg[m[1]],
|
||||
con: con,
|
||||
orig: orig,
|
||||
minorDirty: minorDirty,
|
||||
patchDirty: patchDirty,
|
||||
dirty: dirty,
|
||||
}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// Constraint functions
|
||||
func constraintNotEqual(v *Version, c *constraint) bool {
|
||||
if c.dirty {
|
||||
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.con.Major() != v.Major() {
|
||||
return true
|
||||
}
|
||||
if c.con.Minor() != v.Minor() && !c.minorDirty {
|
||||
return true
|
||||
} else if c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return !v.Equal(c.con)
|
||||
}
|
||||
|
||||
func constraintGreaterThan(v *Version, c *constraint) bool {
|
||||
|
||||
// An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease
|
||||
// exists. This that case.
|
||||
if !isNonZero(c.con) && isNonZero(v) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Compare(c.con) == 1
|
||||
}
|
||||
|
||||
func constraintLessThan(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !c.dirty {
|
||||
return v.Compare(c.con) < 0
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return false
|
||||
} else if v.Minor() > c.con.Minor() && !c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func constraintGreaterThanEqual(v *Version, c *constraint) bool {
|
||||
// An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease
|
||||
// exists. This that case.
|
||||
if !isNonZero(c.con) && isNonZero(v) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Compare(c.con) >= 0
|
||||
}
|
||||
|
||||
func constraintLessThanEqual(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !c.dirty {
|
||||
return v.Compare(c.con) <= 0
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return false
|
||||
} else if v.Minor() > c.con.Minor() && !c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ~*, ~>* --> >= 0.0.0 (any)
|
||||
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
|
||||
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
|
||||
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
|
||||
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
|
||||
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
|
||||
func constraintTilde(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.LessThan(c.con) {
|
||||
return false
|
||||
}
|
||||
|
||||
// ~0.0.0 is a special case where all constraints are accepted. It's
|
||||
// equivalent to >= 0.0.0.
|
||||
if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
|
||||
!c.minorDirty && !c.patchDirty {
|
||||
return true
|
||||
}
|
||||
|
||||
if v.Major() != c.con.Major() {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Minor() != c.con.Minor() && !c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
|
||||
// it's a straight =
|
||||
func constraintTildeOrEqual(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.dirty {
|
||||
c.msg = constraintMsg["~"]
|
||||
return constraintTilde(v, c)
|
||||
}
|
||||
|
||||
return v.Equal(c.con)
|
||||
}
|
||||
|
||||
// ^* --> (any)
|
||||
// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0
|
||||
// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0
|
||||
// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0
|
||||
// ^1.2.3 --> >=1.2.3, <2.0.0
|
||||
// ^1.2.0 --> >=1.2.0, <2.0.0
|
||||
func constraintCaret(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.LessThan(c.con) {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Major() != c.con.Major() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var constraintRangeRegex *regexp.Regexp
|
||||
|
||||
const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
|
||||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||
|
||||
func isX(x string) bool {
|
||||
switch x {
|
||||
case "x", "*", "X":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteRange(i string) string {
|
||||
m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
|
||||
if m == nil {
|
||||
return i
|
||||
}
|
||||
o := i
|
||||
for _, v := range m {
|
||||
t := fmt.Sprintf(">= %s, <= %s", v[1], v[11])
|
||||
o = strings.Replace(o, v[0], t, 1)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// Detect if a version is not zero (0.0.0)
|
||||
func isNonZero(v *Version) bool {
|
||||
if v.Major() != 0 || v.Minor() != 0 || v.Patch() != 0 || v.Prerelease() != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go.
|
||||
|
||||
Specifically it provides the ability to:
|
||||
|
||||
* Parse semantic versions
|
||||
* Sort semantic versions
|
||||
* Check if a semantic version fits within a set of constraints
|
||||
* Optionally work with a `v` prefix
|
||||
|
||||
Parsing Semantic Versions
|
||||
|
||||
To parse a semantic version use the `NewVersion` function. For example,
|
||||
|
||||
v, err := semver.NewVersion("1.2.3-beta.1+build345")
|
||||
|
||||
If there is an error the version wasn't parseable. The version object has methods
|
||||
to get the parts of the version, compare it to other versions, convert the
|
||||
version back into a string, and get the original string. For more details
|
||||
please see the documentation at https://godoc.org/github.com/Masterminds/semver.
|
||||
|
||||
Sorting Semantic Versions
|
||||
|
||||
A set of versions can be sorted using the `sort` package from the standard library.
|
||||
For example,
|
||||
|
||||
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||
vs := make([]*semver.Version, len(raw))
|
||||
for i, r := range raw {
|
||||
v, err := semver.NewVersion(r)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
vs[i] = v
|
||||
}
|
||||
|
||||
sort.Sort(semver.Collection(vs))
|
||||
|
||||
Checking Version Constraints
|
||||
|
||||
Checking a version against version constraints is one of the most featureful
|
||||
parts of the package.
|
||||
|
||||
c, err := semver.NewConstraint(">= 1.2.3")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, _ := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
// Check if the version meets the constraints. The a variable will be true.
|
||||
a := c.Check(v)
|
||||
|
||||
Basic Comparisons
|
||||
|
||||
There are two elements to the comparisons. First, a comparison string is a list
|
||||
of comma separated and comparisons. These are then separated by || separated or
|
||||
comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a
|
||||
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||
greater than or equal to 4.2.3.
|
||||
|
||||
The basic comparisons are:
|
||||
|
||||
* `=`: equal (aliased to no operator)
|
||||
* `!=`: not equal
|
||||
* `>`: greater than
|
||||
* `<`: less than
|
||||
* `>=`: greater than or equal to
|
||||
* `<=`: less than or equal to
|
||||
|
||||
Hyphen Range Comparisons
|
||||
|
||||
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||
These look like:
|
||||
|
||||
* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
|
||||
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5`
|
||||
|
||||
Wildcards In Comparisons
|
||||
|
||||
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||
for all comparison operators. When used on the `=` operator it falls
|
||||
back to the pack level comparison (see tilde below). For example,
|
||||
|
||||
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||
* `<= 2.x` is equivalent to `<= 3`
|
||||
* `*` is equivalent to `>= 0.0.0`
|
||||
|
||||
Tilde Range Comparisons (Patch)
|
||||
|
||||
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||
version is specified and major level changes when the minor number is missing.
|
||||
For example,
|
||||
|
||||
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
|
||||
* `~1` is equivalent to `>= 1, < 2`
|
||||
* `~2.3` is equivalent to `>= 2.3, < 2.4`
|
||||
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `~1.x` is equivalent to `>= 1, < 2`
|
||||
|
||||
Caret Range Comparisons (Major)
|
||||
|
||||
The caret (`^`) comparison operator is for major level changes. This is useful
|
||||
when comparisons of API versions as a major change is API breaking. For example,
|
||||
|
||||
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||
* `^2.3` is equivalent to `>= 2.3, < 3`
|
||||
* `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||
*/
|
||||
package semver
|
|
@ -0,0 +1,421 @@
|
|||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The compiled version of the regex created at init() is cached here so it
|
||||
// only needs to be created once.
|
||||
var versionRegex *regexp.Regexp
|
||||
var validPrereleaseRegex *regexp.Regexp
|
||||
|
||||
var (
|
||||
// ErrInvalidSemVer is returned a version is found to be invalid when
|
||||
// being parsed.
|
||||
ErrInvalidSemVer = errors.New("Invalid Semantic Version")
|
||||
|
||||
// ErrInvalidMetadata is returned when the metadata is an invalid format
|
||||
ErrInvalidMetadata = errors.New("Invalid Metadata string")
|
||||
|
||||
// ErrInvalidPrerelease is returned when the pre-release is an invalid format
|
||||
ErrInvalidPrerelease = errors.New("Invalid Prerelease string")
|
||||
)
|
||||
|
||||
// SemVerRegex is the regular expression used to parse a semantic version.
|
||||
const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
|
||||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||
|
||||
// ValidPrerelease is the regular expression which validates
|
||||
// both prerelease and metadata values.
|
||||
const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)`
|
||||
|
||||
// Version represents a single semantic version.
|
||||
type Version struct {
|
||||
major, minor, patch int64
|
||||
pre string
|
||||
metadata string
|
||||
original string
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionRegex = regexp.MustCompile("^" + SemVerRegex + "$")
|
||||
validPrereleaseRegex = regexp.MustCompile(ValidPrerelease)
|
||||
}
|
||||
|
||||
// NewVersion parses a given version and returns an instance of Version or
|
||||
// an error if unable to parse the version.
|
||||
func NewVersion(v string) (*Version, error) {
|
||||
m := versionRegex.FindStringSubmatch(v)
|
||||
if m == nil {
|
||||
return nil, ErrInvalidSemVer
|
||||
}
|
||||
|
||||
sv := &Version{
|
||||
metadata: m[8],
|
||||
pre: m[5],
|
||||
original: v,
|
||||
}
|
||||
|
||||
var temp int64
|
||||
temp, err := strconv.ParseInt(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||
}
|
||||
sv.major = temp
|
||||
|
||||
if m[2] != "" {
|
||||
temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||
}
|
||||
sv.minor = temp
|
||||
} else {
|
||||
sv.minor = 0
|
||||
}
|
||||
|
||||
if m[3] != "" {
|
||||
temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||
}
|
||||
sv.patch = temp
|
||||
} else {
|
||||
sv.patch = 0
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
// MustParse parses a given version and panics on error.
|
||||
func MustParse(v string) *Version {
|
||||
sv, err := NewVersion(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sv
|
||||
}
|
||||
|
||||
// String converts a Version object to a string.
|
||||
// Note, if the original version contained a leading v this version will not.
|
||||
// See the Original() method to retrieve the original value. Semantic Versions
|
||||
// don't contain a leading v per the spec. Instead it's optional on
|
||||
// impelementation.
|
||||
func (v *Version) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
|
||||
if v.pre != "" {
|
||||
fmt.Fprintf(&buf, "-%s", v.pre)
|
||||
}
|
||||
if v.metadata != "" {
|
||||
fmt.Fprintf(&buf, "+%s", v.metadata)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Original returns the original value passed in to be parsed.
|
||||
func (v *Version) Original() string {
|
||||
return v.original
|
||||
}
|
||||
|
||||
// Major returns the major version.
|
||||
func (v *Version) Major() int64 {
|
||||
return v.major
|
||||
}
|
||||
|
||||
// Minor returns the minor version.
|
||||
func (v *Version) Minor() int64 {
|
||||
return v.minor
|
||||
}
|
||||
|
||||
// Patch returns the patch version.
|
||||
func (v *Version) Patch() int64 {
|
||||
return v.patch
|
||||
}
|
||||
|
||||
// Prerelease returns the pre-release version.
|
||||
func (v *Version) Prerelease() string {
|
||||
return v.pre
|
||||
}
|
||||
|
||||
// Metadata returns the metadata on the version.
|
||||
func (v *Version) Metadata() string {
|
||||
return v.metadata
|
||||
}
|
||||
|
||||
// originalVPrefix returns the original 'v' prefix if any.
|
||||
func (v *Version) originalVPrefix() string {
|
||||
|
||||
// Note, only lowercase v is supported as a prefix by the parser.
|
||||
if v.original != "" && v.original[:1] == "v" {
|
||||
return v.original[:1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IncPatch produces the next patch version.
|
||||
// If the current version does not have prerelease/metadata information,
|
||||
// it unsets metadata and prerelease values, increments patch number.
|
||||
// If the current version has any of prerelease or metadata information,
|
||||
// it unsets both values and keeps curent patch value
|
||||
func (v Version) IncPatch() Version {
|
||||
vNext := v
|
||||
// according to http://semver.org/#spec-item-9
|
||||
// Pre-release versions have a lower precedence than the associated normal version.
|
||||
// according to http://semver.org/#spec-item-10
|
||||
// Build metadata SHOULD be ignored when determining version precedence.
|
||||
if v.pre != "" {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
} else {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = v.patch + 1
|
||||
}
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// IncMinor produces the next minor version.
|
||||
// Sets patch to 0.
|
||||
// Increments minor number.
|
||||
// Unsets metadata.
|
||||
// Unsets prerelease status.
|
||||
func (v Version) IncMinor() Version {
|
||||
vNext := v
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
vNext.minor = v.minor + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// IncMajor produces the next major version.
|
||||
// Sets patch to 0.
|
||||
// Sets minor to 0.
|
||||
// Increments major number.
|
||||
// Unsets metadata.
|
||||
// Unsets prerelease status.
|
||||
func (v Version) IncMajor() Version {
|
||||
vNext := v
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
vNext.minor = 0
|
||||
vNext.major = v.major + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// SetPrerelease defines the prerelease value.
|
||||
// Value must not include the required 'hypen' prefix.
|
||||
func (v Version) SetPrerelease(prerelease string) (Version, error) {
|
||||
vNext := v
|
||||
if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) {
|
||||
return vNext, ErrInvalidPrerelease
|
||||
}
|
||||
vNext.pre = prerelease
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext, nil
|
||||
}
|
||||
|
||||
// SetMetadata defines metadata value.
|
||||
// Value must not include the required 'plus' prefix.
|
||||
func (v Version) SetMetadata(metadata string) (Version, error) {
|
||||
vNext := v
|
||||
if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) {
|
||||
return vNext, ErrInvalidMetadata
|
||||
}
|
||||
vNext.metadata = metadata
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext, nil
|
||||
}
|
||||
|
||||
// LessThan tests if one version is less than another one.
|
||||
func (v *Version) LessThan(o *Version) bool {
|
||||
return v.Compare(o) < 0
|
||||
}
|
||||
|
||||
// GreaterThan tests if one version is greater than another one.
|
||||
func (v *Version) GreaterThan(o *Version) bool {
|
||||
return v.Compare(o) > 0
|
||||
}
|
||||
|
||||
// Equal tests if two versions are equal to each other.
|
||||
// Note, versions can be equal with different metadata since metadata
|
||||
// is not considered part of the comparable version.
|
||||
func (v *Version) Equal(o *Version) bool {
|
||||
return v.Compare(o) == 0
|
||||
}
|
||||
|
||||
// Compare compares this version to another one. It returns -1, 0, or 1 if
|
||||
// the version smaller, equal, or larger than the other version.
|
||||
//
|
||||
// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
|
||||
// lower than the version without a prerelease.
|
||||
func (v *Version) Compare(o *Version) int {
|
||||
// Compare the major, minor, and patch version for differences. If a
|
||||
// difference is found return the comparison.
|
||||
if d := compareSegment(v.Major(), o.Major()); d != 0 {
|
||||
return d
|
||||
}
|
||||
if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
|
||||
return d
|
||||
}
|
||||
if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
|
||||
return d
|
||||
}
|
||||
|
||||
// At this point the major, minor, and patch versions are the same.
|
||||
ps := v.pre
|
||||
po := o.Prerelease()
|
||||
|
||||
if ps == "" && po == "" {
|
||||
return 0
|
||||
}
|
||||
if ps == "" {
|
||||
return 1
|
||||
}
|
||||
if po == "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
return comparePrerelease(ps, po)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements JSON.Unmarshaler interface.
|
||||
func (v *Version) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
temp, err := NewVersion(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.major = temp.major
|
||||
v.minor = temp.minor
|
||||
v.patch = temp.patch
|
||||
v.pre = temp.pre
|
||||
v.metadata = temp.metadata
|
||||
v.original = temp.original
|
||||
temp = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements JSON.Marshaler interface.
|
||||
func (v *Version) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.String())
|
||||
}
|
||||
|
||||
func compareSegment(v, o int64) int {
|
||||
if v < o {
|
||||
return -1
|
||||
}
|
||||
if v > o {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func comparePrerelease(v, o string) int {
|
||||
|
||||
// split the prelease versions by their part. The separator, per the spec,
|
||||
// is a .
|
||||
sparts := strings.Split(v, ".")
|
||||
oparts := strings.Split(o, ".")
|
||||
|
||||
// Find the longer length of the parts to know how many loop iterations to
|
||||
// go through.
|
||||
slen := len(sparts)
|
||||
olen := len(oparts)
|
||||
|
||||
l := slen
|
||||
if olen > slen {
|
||||
l = olen
|
||||
}
|
||||
|
||||
// Iterate over each part of the prereleases to compare the differences.
|
||||
for i := 0; i < l; i++ {
|
||||
// Since the lentgh of the parts can be different we need to create
|
||||
// a placeholder. This is to avoid out of bounds issues.
|
||||
stemp := ""
|
||||
if i < slen {
|
||||
stemp = sparts[i]
|
||||
}
|
||||
|
||||
otemp := ""
|
||||
if i < olen {
|
||||
otemp = oparts[i]
|
||||
}
|
||||
|
||||
d := comparePrePart(stemp, otemp)
|
||||
if d != 0 {
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
||||
// Reaching here means two versions are of equal value but have different
|
||||
// metadata (the part following a +). They are not identical in string form
|
||||
// but the version comparison finds them to be equal.
|
||||
return 0
|
||||
}
|
||||
|
||||
func comparePrePart(s, o string) int {
|
||||
// Fastpath if they are equal
|
||||
if s == o {
|
||||
return 0
|
||||
}
|
||||
|
||||
// When s or o are empty we can use the other in an attempt to determine
|
||||
// the response.
|
||||
if s == "" {
|
||||
if o != "" {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if o == "" {
|
||||
if s != "" {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// When comparing strings "99" is greater than "103". To handle
|
||||
// cases like this we need to detect numbers and compare them.
|
||||
|
||||
oi, n1 := strconv.ParseInt(o, 10, 64)
|
||||
si, n2 := strconv.ParseInt(s, 10, 64)
|
||||
|
||||
// The case where both are strings compare the strings
|
||||
if n1 != nil && n2 != nil {
|
||||
if s > o {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
} else if n1 != nil {
|
||||
// o is a string and s is a number
|
||||
return -1
|
||||
} else if n2 != nil {
|
||||
// s is a string and o is a number
|
||||
return 1
|
||||
}
|
||||
// Both are numbers
|
||||
if si > oi {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
_workspace
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
vendor
|
||||
|
||||
# GoLand
|
||||
.idea
|
|
@ -0,0 +1,14 @@
|
|||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- "1.10"
|
||||
- "1.11"
|
||||
|
||||
script:
|
||||
- FILES=`find . -iname '*.go' -type f -not -path "./vendor/*"`
|
||||
# linting
|
||||
- gofmt -d $FILES
|
||||
- env GO111MODULE=on go tool vet $FILES
|
||||
# testing
|
||||
- go generate
|
||||
- env GO111MODULE=on go test -v -race
|
|
@ -0,0 +1,174 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Masterminds/semver"
|
||||
packages = ["."]
|
||||
revision = "c7af12943936e8c39859482e61f0574c2fd7fc75"
|
||||
version = "v1.4.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/cloudfoundry/gofileutils"
|
||||
packages = ["fileutils"]
|
||||
revision = "4d0c80011a0f37da1711c184028bc40137cd45af"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/codegangsta/inject"
|
||||
packages = ["."]
|
||||
revision = "37d7f8432a3e684eef9b2edece76bdfa6ac85b39"
|
||||
version = "v1.0-rc1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-martini/martini"
|
||||
packages = ["."]
|
||||
revision = "49411a5b646861ad29a6ddd5351717a0a9c49b94"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/gopherjs/gopherjs"
|
||||
packages = ["js"]
|
||||
revision = "444abdf920945de5d4a977b572bcc6c674d1e4eb"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jtolds/gls"
|
||||
packages = ["."]
|
||||
revision = "77f18212c9c7edc9bd6a33d383a7b545ce62f064"
|
||||
version = "v4.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/martini-contrib/render"
|
||||
packages = ["."]
|
||||
revision = "ec18f8345a1181146728238980606fb1d6f40e8c"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/onsi/gomega"
|
||||
packages = [
|
||||
".",
|
||||
"format",
|
||||
"internal/assertion",
|
||||
"internal/asyncassertion",
|
||||
"internal/oraclematcher",
|
||||
"internal/testingtsupport",
|
||||
"matchers",
|
||||
"matchers/support/goraph/bipartitegraph",
|
||||
"matchers/support/goraph/edge",
|
||||
"matchers/support/goraph/node",
|
||||
"matchers/support/goraph/util",
|
||||
"types"
|
||||
]
|
||||
revision = "c893efa28eb45626cdaa76c9f653b62488858837"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/oxtoacart/bpool"
|
||||
packages = ["."]
|
||||
revision = "4e1c5567d7c2dd59fa4c7c83d34c2f3528b025d6"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/smartystreets/assertions"
|
||||
packages = [
|
||||
".",
|
||||
"internal/go-render/render",
|
||||
"internal/oglematchers"
|
||||
]
|
||||
revision = "ff1918e1e5a13a74014644ae7c1e0ba2f791364d"
|
||||
version = "1.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/smartystreets/goconvey"
|
||||
packages = [
|
||||
"convey",
|
||||
"convey/gotest",
|
||||
"convey/reporting"
|
||||
]
|
||||
revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857"
|
||||
version = "1.6.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"html",
|
||||
"html/atom",
|
||||
"html/charset"
|
||||
]
|
||||
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"clientcredentials",
|
||||
"internal"
|
||||
]
|
||||
revision = "f95fa95eaa936d9d87489b15d1d18b97c1ba9c28"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"encoding",
|
||||
"encoding/charmap",
|
||||
"encoding/htmlindex",
|
||||
"encoding/internal",
|
||||
"encoding/internal/identifier",
|
||||
"encoding/japanese",
|
||||
"encoding/korean",
|
||||
"encoding/simplifiedchinese",
|
||||
"encoding/traditionalchinese",
|
||||
"encoding/unicode",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/utf8internal",
|
||||
"language",
|
||||
"runes",
|
||||
"transform",
|
||||
"unicode/cldr"
|
||||
]
|
||||
revision = "88f656faf3f37f690df1a32515b479415e1a6769"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
"internal",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch"
|
||||
]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "287cf08546ab5e7e37d55a84f7ed3fd1db036de5"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "2e35689146470eb531e3645c63fb933ad86066e63f57021c20e592e25299a02b"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-martini/martini"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/martini-contrib/render"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/onsi/gomega"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/smartystreets/goconvey"
|
||||
version = "1.6.3"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/Masterminds/semver"
|
||||
version = "1.4.2"
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Long Nguyen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,59 @@
|
|||
# go-cfclient
|
||||
[![Travis-CI](https://travis-ci.org/cloudfoundry-community/go-cfclient.svg)](https://travis-ci.org/cloudfoundry-community/go-cfclient)
|
||||
[![GoDoc](https://godoc.org/github.com/cloudfoundry-community/go-cfclient?status.svg)](http://godoc.org/github.com/cloudfoundry-community/go-cfclient)
|
||||
[![Report card](https://goreportcard.com/badge/github.com/cloudfoundry-community/go-cfclient)](https://goreportcard.com/report/github.com/cloudfoundry-community/go-cfclient)
|
||||
|
||||
### Overview
|
||||
|
||||
`cfclient` is a package to assist you in writing apps that need to interact with [Cloud Foundry](http://cloudfoundry.org).
|
||||
It provides functions and structures to retrieve and update
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
go get github.com/cloudfoundry-community/go-cfclient
|
||||
```
|
||||
|
||||
NOTE: Currently this project is not versioning its releases and so breaking changes might be introduced.
|
||||
Whilst hopefully notifications of breaking changes are made via commit messages, ideally your project will use a local
|
||||
vendoring system to lock in a version of `go-cfclient` that is known to work for you.
|
||||
This will allow you to control the timing and maintenance of upgrades to newer versions of this library.
|
||||
|
||||
Some example code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/cloudfoundry-community/go-cfclient"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c := &cfclient.Config{
|
||||
ApiAddress: "https://api.10.244.0.34.xip.io",
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
}
|
||||
client, _ := cfclient.NewClient(c)
|
||||
apps, _ := client.ListApps()
|
||||
fmt.Println(apps)
|
||||
}
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
#### Errors
|
||||
|
||||
If the Cloud Foundry error definitions change at <https://github.com/cloudfoundry/cloud_controller_ng/blob/master/vendor/errors/v2.yml>
|
||||
then the error predicate functions in this package need to be regenerated.
|
||||
|
||||
To do this, simply use Go to regenerate the code:
|
||||
|
||||
```
|
||||
go generate
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
Pull requests welcome.
|
107
vendor/github.com/cloudfoundry-community/go-cfclient/app_update.go
generated
vendored
Normal file
107
vendor/github.com/cloudfoundry-community/go-cfclient/app_update.go
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type UpdateResponse struct {
|
||||
Metadata Meta `json:"metadata"`
|
||||
Entity UpdateResponseEntity `json:"entity"`
|
||||
}
|
||||
type AppUpdateResource struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Memory int `json:"memory,omitempty"`
|
||||
Instances int `json:"instances,omitempty"`
|
||||
DiskQuota int `json:"disk_quota,omitempty"`
|
||||
SpaceGuid string `json:"space_guid,omitempty"`
|
||||
StackGuid string `json:"stack_guid,omitempty"`
|
||||
State AppState `json:"state,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
Buildpack string `json:"buildpack,omitempty"`
|
||||
HealthCheckHttpEndpoint string `json:"health_check_http_endpoint,omitempty"`
|
||||
HealthCheckType string `json:"health_check_type,omitempty"`
|
||||
HealthCheckTimeout int `json:"health_check_timeout,omitempty"`
|
||||
Diego bool `json:"diego,omitempty"`
|
||||
EnableSSH bool `json:"enable_ssh,omitempty"`
|
||||
DockerImage string `json:"docker_image,omitempty"`
|
||||
DockerCredentials map[string]interface{} `json:"docker_credentials_json,omitempty"`
|
||||
Environment map[string]interface{} `json:"environment_json,omitempty"`
|
||||
StagingFailedReason string `json:"staging_failed_reason,omitempty"`
|
||||
StagingFailedDescription string `json:"staging_failed_description,omitempty"`
|
||||
Ports []int `json:"ports,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateResponseEntity struct {
|
||||
Name string `json:"name"`
|
||||
Production bool `json:"production"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
StackGuid string `json:"stack_guid"`
|
||||
Buildpack string `json:"buildpack"`
|
||||
DetectedBuildpack string `json:"detected_buildpack"`
|
||||
DetectedBuildpackGuid string `json:"detected_buildpack_guid"`
|
||||
Environment map[string]interface{} `json:"environment_json"`
|
||||
Memory int `json:"memory"`
|
||||
Instances int `json:"instances"`
|
||||
DiskQuota int `json:"disk_quota"`
|
||||
State string `json:"state"`
|
||||
Version string `json:"version"`
|
||||
Command string `json:"command"`
|
||||
Console bool `json:"console"`
|
||||
Debug string `json:"debug"`
|
||||
StagingTaskId string `json:"staging_task_id"`
|
||||
PackageState string `json:"package_state"`
|
||||
HealthCheckHttpEndpoint string `json:"health_check_http_endpoint"`
|
||||
HealthCheckType string `json:"health_check_type"`
|
||||
HealthCheckTimeout int `json:"health_check_timeout"`
|
||||
StagingFailedReason string `json:"staging_failed_reason"`
|
||||
StagingFailedDescription string `json:"staging_failed_description"`
|
||||
Diego bool `json:"diego,omitempty"`
|
||||
DockerImage string `json:"docker_image"`
|
||||
DockerCredentials struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
} `json:"docker_credentials"`
|
||||
PackageUpdatedAt string `json:"package_updated_at"`
|
||||
DetectedStartCommand string `json:"detected_start_command"`
|
||||
EnableSSH bool `json:"enable_ssh"`
|
||||
Ports []int `json:"ports"`
|
||||
SpaceURL string `json:"space_url"`
|
||||
StackURL string `json:"stack_url"`
|
||||
RoutesURL string `json:"routes_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
ServiceBindingsUrl string `json:"service_bindings_url"`
|
||||
RouteMappingsUrl string `json:"route_mappings_url"`
|
||||
}
|
||||
|
||||
func (c *Client) UpdateApp(guid string, aur AppUpdateResource) (UpdateResponse, error) {
|
||||
var updateResponse UpdateResponse
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(aur)
|
||||
if err != nil {
|
||||
return UpdateResponse{}, err
|
||||
}
|
||||
req := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/apps/%s", guid), buf)
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return UpdateResponse{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return UpdateResponse{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return UpdateResponse{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &updateResponse)
|
||||
if err != nil {
|
||||
return UpdateResponse{}, err
|
||||
}
|
||||
return updateResponse, nil
|
||||
}
|
80
vendor/github.com/cloudfoundry-community/go-cfclient/app_usage_events.go
generated
vendored
Normal file
80
vendor/github.com/cloudfoundry-community/go-cfclient/app_usage_events.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AppUsageEvent struct {
|
||||
GUID string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
State string `json:"state"`
|
||||
PreviousState string `json:"previous_state"`
|
||||
MemoryInMbPerInstance int `json:"memory_in_mb_per_instance"`
|
||||
PreviousMemoryInMbPerInstance int `json:"previous_memory_in_mb_per_instance"`
|
||||
InstanceCount int `json:"instance_count"`
|
||||
PreviousInstanceCount int `json:"previous_instance_count"`
|
||||
AppGUID string `json:"app_guid"`
|
||||
SpaceGUID string `json:"space_guid"`
|
||||
SpaceName string `json:"space_name"`
|
||||
OrgGUID string `json:"org_guid"`
|
||||
BuildpackGUID string `json:"buildpack_guid"`
|
||||
BuildpackName string `json:"buildpack_name"`
|
||||
PackageState string `json:"package_state"`
|
||||
PreviousPackageState string `json:"previous_package_state"`
|
||||
ParentAppGUID string `json:"parent_app_guid"`
|
||||
ParentAppName string `json:"parent_app_name"`
|
||||
ProcessType string `json:"process_type"`
|
||||
TaskName string `json:"task_name"`
|
||||
TaskGUID string `json:"task_guid"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type AppUsageEventsResponse struct {
|
||||
TotalResults int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextURL string `json:"next_url"`
|
||||
Resources []AppUsageEventResource `json:"resources"`
|
||||
}
|
||||
|
||||
type AppUsageEventResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity AppUsageEvent `json:"entity"`
|
||||
}
|
||||
|
||||
// ListAppUsageEventsByQuery lists all events matching the provided query.
|
||||
func (c *Client) ListAppUsageEventsByQuery(query url.Values) ([]AppUsageEvent, error) {
|
||||
var appUsageEvents []AppUsageEvent
|
||||
requestURL := fmt.Sprintf("/v2/app_usage_events?%s", query.Encode())
|
||||
for {
|
||||
var appUsageEventsResponse AppUsageEventsResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error requesting events")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if err := json.NewDecoder(resp.Body).Decode(&appUsageEventsResponse); err != nil {
|
||||
return nil, errors.Wrap(err, "error unmarshaling events")
|
||||
}
|
||||
for _, e := range appUsageEventsResponse.Resources {
|
||||
e.Entity.GUID = e.Meta.Guid
|
||||
e.Entity.CreatedAt = e.Meta.CreatedAt
|
||||
e.Entity.c = c
|
||||
appUsageEvents = append(appUsageEvents, e.Entity)
|
||||
}
|
||||
requestURL = appUsageEventsResponse.NextURL
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return appUsageEvents, nil
|
||||
}
|
||||
|
||||
// ListAppUsageEvents lists all unfiltered events.
|
||||
func (c *Client) ListAppUsageEvents() ([]AppUsageEvent, error) {
|
||||
return c.ListAppUsageEventsByQuery(nil)
|
||||
}
|
182
vendor/github.com/cloudfoundry-community/go-cfclient/appevents.go
generated
vendored
Normal file
182
vendor/github.com/cloudfoundry-community/go-cfclient/appevents.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
//AppCrash app.crash event const
|
||||
AppCrash = "app.crash"
|
||||
//AppStart audit.app.start event const
|
||||
AppStart = "audit.app.start"
|
||||
//AppStop audit.app.stop event const
|
||||
AppStop = "audit.app.stop"
|
||||
//AppUpdate audit.app.update event const
|
||||
AppUpdate = "audit.app.update"
|
||||
//AppCreate audit.app.create event const
|
||||
AppCreate = "audit.app.create"
|
||||
//AppDelete audit.app.delete-request event const
|
||||
AppDelete = "audit.app.delete-request"
|
||||
//AppSSHAuth audit.app.ssh-authorized event const
|
||||
AppSSHAuth = "audit.app.ssh-authorized"
|
||||
//AppSSHUnauth audit.app.ssh-unauthorized event const
|
||||
AppSSHUnauth = "audit.app.ssh-unauthorized"
|
||||
//AppRestage audit.app.restage event const
|
||||
AppRestage = "audit.app.restage"
|
||||
//AppMapRoute audit.app.map-route event const
|
||||
AppMapRoute = "audit.app.map-route"
|
||||
//AppUnmapRoute audit.app.unmap-route event const
|
||||
AppUnmapRoute = "audit.app.unmap-route"
|
||||
//FilterTimestamp const for query filter timestamp
|
||||
FilterTimestamp = "timestamp"
|
||||
//FilterActee const for query filter actee
|
||||
FilterActee = "actee"
|
||||
)
|
||||
|
||||
//ValidOperators const for all valid operators in a query
|
||||
var ValidOperators = []string{":", ">=", "<=", "<", ">", "IN"}
|
||||
|
||||
// AppEventResponse the entire response
|
||||
type AppEventResponse struct {
|
||||
Results int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
PrevURL string `json:"prev_url"`
|
||||
NextURL string `json:"next_url"`
|
||||
Resources []AppEventResource `json:"resources"`
|
||||
}
|
||||
|
||||
// AppEventResource the event resources
|
||||
type AppEventResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity AppEventEntity `json:"entity"`
|
||||
}
|
||||
|
||||
//AppEventQuery a struct for defining queries like 'q=filter>value' or 'q=filter IN a,b,c'
|
||||
type AppEventQuery struct {
|
||||
Filter string
|
||||
Operator string
|
||||
Value string
|
||||
}
|
||||
|
||||
// The AppEventEntity the actual app event body
|
||||
type AppEventEntity struct {
|
||||
//EventTypes are app.crash, audit.app.start, audit.app.stop, audit.app.update, audit.app.create, audit.app.delete-request
|
||||
EventType string `json:"type"`
|
||||
//The GUID of the actor.
|
||||
Actor string `json:"actor"`
|
||||
//The actor type, user or app
|
||||
ActorType string `json:"actor_type"`
|
||||
//The name of the actor.
|
||||
ActorName string `json:"actor_name"`
|
||||
//The GUID of the actee.
|
||||
Actee string `json:"actee"`
|
||||
//The actee type, space, app or v3-app
|
||||
ActeeType string `json:"actee_type"`
|
||||
//The name of the actee.
|
||||
ActeeName string `json:"actee_name"`
|
||||
//Timestamp format "2016-02-26T13:29:44Z". The event creation time.
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
MetaData struct {
|
||||
//app.crash event fields
|
||||
ExitDescription string `json:"exit_description,omitempty"`
|
||||
ExitReason string `json:"reason,omitempty"`
|
||||
ExitStatus string `json:"exit_status,omitempty"`
|
||||
|
||||
Request struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Instances float64 `json:"instances,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Memory float64 `json:"memory,omitempty"`
|
||||
EnvironmentVars string `json:"environment_json,omitempty"`
|
||||
DockerCredentials string `json:"docker_credentials_json,omitempty"`
|
||||
//audit.app.create event fields
|
||||
Console bool `json:"console,omitempty"`
|
||||
Buildpack string `json:"buildpack,omitempty"`
|
||||
Space string `json:"space_guid,omitempty"`
|
||||
HealthcheckType string `json:"health_check_type,omitempty"`
|
||||
HealthcheckTimeout float64 `json:"health_check_timeout,omitempty"`
|
||||
Production bool `json:"production,omitempty"`
|
||||
//app.crash event fields
|
||||
Index float64 `json:"index,omitempty"`
|
||||
} `json:"request"`
|
||||
} `json:"metadata"`
|
||||
}
|
||||
|
||||
// ListAppEvents returns all app events based on eventType
|
||||
func (c *Client) ListAppEvents(eventType string) ([]AppEventEntity, error) {
|
||||
return c.ListAppEventsByQuery(eventType, nil)
|
||||
}
|
||||
|
||||
// ListAppEventsByQuery returns all app events based on eventType and queries
|
||||
func (c *Client) ListAppEventsByQuery(eventType string, queries []AppEventQuery) ([]AppEventEntity, error) {
|
||||
var events []AppEventEntity
|
||||
|
||||
if eventType != AppCrash && eventType != AppStart && eventType != AppStop && eventType != AppUpdate && eventType != AppCreate &&
|
||||
eventType != AppDelete && eventType != AppSSHAuth && eventType != AppSSHUnauth && eventType != AppRestage &&
|
||||
eventType != AppMapRoute && eventType != AppUnmapRoute {
|
||||
return nil, errors.New("Unsupported app event type " + eventType)
|
||||
}
|
||||
|
||||
var query = "/v2/events?q=type:" + eventType
|
||||
//adding the additional queries
|
||||
if queries != nil && len(queries) > 0 {
|
||||
for _, eventQuery := range queries {
|
||||
if eventQuery.Filter != FilterTimestamp && eventQuery.Filter != FilterActee {
|
||||
return nil, errors.New("Unsupported query filter type " + eventQuery.Filter)
|
||||
}
|
||||
if !stringInSlice(eventQuery.Operator, ValidOperators) {
|
||||
return nil, errors.New("Unsupported query operator type " + eventQuery.Operator)
|
||||
}
|
||||
query += "&q=" + eventQuery.Filter + eventQuery.Operator + eventQuery.Value
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
eventResponse, err := c.getAppEventsResponse(query)
|
||||
if err != nil {
|
||||
return []AppEventEntity{}, err
|
||||
}
|
||||
for _, event := range eventResponse.Resources {
|
||||
events = append(events, event.Entity)
|
||||
}
|
||||
query = eventResponse.NextURL
|
||||
if query == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (c *Client) getAppEventsResponse(query string) (AppEventResponse, error) {
|
||||
var eventResponse AppEventResponse
|
||||
r := c.NewRequest("GET", query)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return AppEventResponse{}, errors.Wrap(err, "Error requesting appevents")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return AppEventResponse{}, errors.Wrap(err, "Error reading appevents response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &eventResponse)
|
||||
if err != nil {
|
||||
return AppEventResponse{}, errors.Wrap(err, "Error unmarshalling appevent")
|
||||
}
|
||||
return eventResponse, nil
|
||||
}
|
||||
|
||||
func stringInSlice(str string, list []string) bool {
|
||||
for _, v := range list {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,659 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AppResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []AppResource `json:"resources"`
|
||||
}
|
||||
|
||||
type AppResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity App `json:"entity"`
|
||||
}
|
||||
|
||||
type AppState string
|
||||
|
||||
const (
|
||||
APP_STOPPED AppState = "STOPPED"
|
||||
APP_STARTED AppState = "STARTED"
|
||||
)
|
||||
|
||||
type HealthCheckType string
|
||||
|
||||
const (
|
||||
HEALTH_HTTP HealthCheckType = "http"
|
||||
HEALTH_PORT HealthCheckType = "port"
|
||||
HEALTH_PROCESS HealthCheckType = "process"
|
||||
)
|
||||
|
||||
type DockerCredentials struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type AppCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
// Memory for the app, in MB
|
||||
Memory int `json:"memory,omitempty"`
|
||||
// Instances to startup
|
||||
Instances int `json:"instances,omitempty"`
|
||||
// Disk quota in MB
|
||||
DiskQuota int `json:"disk_quota,omitempty"`
|
||||
StackGuid string `json:"stack_guid,omitempty"`
|
||||
// Desired state of the app. Either "STOPPED" or "STARTED"
|
||||
State AppState `json:"state,omitempty"`
|
||||
// Command to start an app
|
||||
Command string `json:"command,omitempty"`
|
||||
// Buildpack to build the app. Three options:
|
||||
// 1. Blank for autodetection
|
||||
// 2. GIT url
|
||||
// 3. Name of an installed buildpack
|
||||
Buildpack string `json:"buildpack,omitempty"`
|
||||
// Endpoint to check if an app is healthy
|
||||
HealthCheckHttpEndpoint string `json:"health_check_http_endpoint,omitempty"`
|
||||
// How to check if an app is healthy. Defaults to HEALTH_PORT if not specified
|
||||
HealthCheckType HealthCheckType `json:"health_check_type,omitempty"`
|
||||
Diego bool `json:"diego,omitempty"`
|
||||
EnableSSH bool `json:"enable_ssh,omitempty"`
|
||||
DockerImage string `json:"docker_image,omitempty"`
|
||||
DockerCredentials DockerCredentials `json:"docker_credentials,omitempty"`
|
||||
Environment map[string]interface{} `json:"environment_json,omitempty"`
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Name string `json:"name"`
|
||||
Memory int `json:"memory"`
|
||||
Instances int `json:"instances"`
|
||||
DiskQuota int `json:"disk_quota"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
StackGuid string `json:"stack_guid"`
|
||||
State string `json:"state"`
|
||||
PackageState string `json:"package_state"`
|
||||
Command string `json:"command"`
|
||||
Buildpack string `json:"buildpack"`
|
||||
DetectedBuildpack string `json:"detected_buildpack"`
|
||||
DetectedBuildpackGuid string `json:"detected_buildpack_guid"`
|
||||
HealthCheckHttpEndpoint string `json:"health_check_http_endpoint"`
|
||||
HealthCheckType string `json:"health_check_type"`
|
||||
HealthCheckTimeout int `json:"health_check_timeout"`
|
||||
Diego bool `json:"diego"`
|
||||
EnableSSH bool `json:"enable_ssh"`
|
||||
DetectedStartCommand string `json:"detected_start_command"`
|
||||
DockerImage string `json:"docker_image"`
|
||||
DockerCredentials map[string]interface{} `json:"docker_credentials_json"`
|
||||
Environment map[string]interface{} `json:"environment_json"`
|
||||
StagingFailedReason string `json:"staging_failed_reason"`
|
||||
StagingFailedDescription string `json:"staging_failed_description"`
|
||||
Ports []int `json:"ports"`
|
||||
SpaceURL string `json:"space_url"`
|
||||
SpaceData SpaceResource `json:"space"`
|
||||
PackageUpdatedAt string `json:"package_updated_at"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type AppInstance struct {
|
||||
State string `json:"state"`
|
||||
Since sinceTime `json:"since"`
|
||||
}
|
||||
|
||||
type AppStats struct {
|
||||
State string `json:"state"`
|
||||
Stats struct {
|
||||
Name string `json:"name"`
|
||||
Uris []string `json:"uris"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Uptime int `json:"uptime"`
|
||||
MemQuota int `json:"mem_quota"`
|
||||
DiskQuota int `json:"disk_quota"`
|
||||
FdsQuota int `json:"fds_quota"`
|
||||
Usage struct {
|
||||
Time statTime `json:"time"`
|
||||
CPU float64 `json:"cpu"`
|
||||
Mem int `json:"mem"`
|
||||
Disk int `json:"disk"`
|
||||
} `json:"usage"`
|
||||
} `json:"stats"`
|
||||
}
|
||||
|
||||
type AppSummary struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
ServiceCount int `json:"service_count"`
|
||||
RunningInstances int `json:"running_instances"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
StackGuid string `json:"stack_guid"`
|
||||
Buildpack string `json:"buildpack"`
|
||||
DetectedBuildpack string `json:"detected_buildpack"`
|
||||
Environment map[string]interface{} `json:"environment_json"`
|
||||
Memory int `json:"memory"`
|
||||
Instances int `json:"instances"`
|
||||
DiskQuota int `json:"disk_quota"`
|
||||
State string `json:"state"`
|
||||
Command string `json:"command"`
|
||||
PackageState string `json:"package_state"`
|
||||
HealthCheckType string `json:"health_check_type"`
|
||||
HealthCheckTimeout int `json:"health_check_timeout"`
|
||||
StagingFailedReason string `json:"staging_failed_reason"`
|
||||
StagingFailedDescription string `json:"staging_failed_description"`
|
||||
Diego bool `json:"diego"`
|
||||
DockerImage string `json:"docker_image"`
|
||||
DetectedStartCommand string `json:"detected_start_command"`
|
||||
EnableSSH bool `json:"enable_ssh"`
|
||||
DockerCredentials map[string]interface{} `json:"docker_credentials_json"`
|
||||
}
|
||||
|
||||
type AppEnv struct {
|
||||
// These can have arbitrary JSON so need to map to interface{}
|
||||
Environment map[string]interface{} `json:"environment_json"`
|
||||
StagingEnv map[string]interface{} `json:"staging_env_json"`
|
||||
RunningEnv map[string]interface{} `json:"running_env_json"`
|
||||
SystemEnv map[string]interface{} `json:"system_env_json"`
|
||||
ApplicationEnv map[string]interface{} `json:"application_env_json"`
|
||||
}
|
||||
|
||||
// Custom time types to handle non-RFC3339 formatting in API JSON
|
||||
|
||||
type sinceTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (s *sinceTime) UnmarshalJSON(b []byte) (err error) {
|
||||
timeFlt, err := strconv.ParseFloat(string(b), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time := time.Unix(int64(timeFlt), 0)
|
||||
*s = sinceTime{time}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s sinceTime) ToTime() time.Time {
|
||||
t, _ := time.Parse(time.UnixDate, s.Format(time.UnixDate))
|
||||
return t
|
||||
}
|
||||
|
||||
type statTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (s *statTime) UnmarshalJSON(b []byte) (err error) {
|
||||
timeString, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
possibleFormats := [...]string{time.RFC3339, time.RFC3339Nano, "2006-01-02 15:04:05 -0700", "2006-01-02 15:04:05 MST"}
|
||||
|
||||
var value time.Time
|
||||
for _, possibleFormat := range possibleFormats {
|
||||
if value, err = time.Parse(possibleFormat, timeString); err == nil {
|
||||
*s = statTime{value}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s was not in any of the expected Date Formats %v", timeString, possibleFormats)
|
||||
}
|
||||
|
||||
func (s statTime) ToTime() time.Time {
|
||||
t, _ := time.Parse(time.UnixDate, s.Format(time.UnixDate))
|
||||
return t
|
||||
}
|
||||
|
||||
func (a *App) Space() (Space, error) {
|
||||
var spaceResource SpaceResource
|
||||
r := a.c.NewRequest("GET", a.SpaceURL)
|
||||
resp, err := a.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Space{}, errors.Wrap(err, "Error requesting space")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return Space{}, errors.Wrap(err, "Error reading space response")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &spaceResource)
|
||||
if err != nil {
|
||||
return Space{}, errors.Wrap(err, "Error unmarshalling body")
|
||||
}
|
||||
return a.c.mergeSpaceResource(spaceResource), nil
|
||||
}
|
||||
|
||||
func (a *App) Summary() (AppSummary, error) {
|
||||
var appSummary AppSummary
|
||||
requestUrl := fmt.Sprintf("/v2/apps/%s/summary", a.Guid)
|
||||
r := a.c.NewRequest("GET", requestUrl)
|
||||
resp, err := a.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return AppSummary{}, errors.Wrap(err, "Error requesting app summary")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return AppSummary{}, errors.Wrap(err, "Error reading app summary body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &appSummary)
|
||||
if err != nil {
|
||||
return AppSummary{}, errors.Wrap(err, "Error unmarshalling app summary")
|
||||
}
|
||||
return appSummary, nil
|
||||
}
|
||||
|
||||
// ListAppsByQueryWithLimits queries totalPages app info. When totalPages is
|
||||
// less and equal than 0, it queries all app info
|
||||
// When there are no more than totalPages apps on server side, all apps info will be returned
|
||||
func (c *Client) ListAppsByQueryWithLimits(query url.Values, totalPages int) ([]App, error) {
|
||||
return c.listApps("/v2/apps?"+query.Encode(), totalPages)
|
||||
}
|
||||
|
||||
func (c *Client) ListAppsByQuery(query url.Values) ([]App, error) {
|
||||
return c.listApps("/v2/apps?"+query.Encode(), -1)
|
||||
}
|
||||
|
||||
// GetAppByGuidNoInlineCall will fetch app info including space and orgs information
|
||||
// Without using inline-relations-depth=2 call
|
||||
func (c *Client) GetAppByGuidNoInlineCall(guid string) (App, error) {
|
||||
var appResource AppResource
|
||||
r := c.NewRequest("GET", "/v2/apps/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrap(err, "Error requesting apps")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrap(err, "Error reading app response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &appResource)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrap(err, "Error unmarshalling app")
|
||||
}
|
||||
app := c.mergeAppResource(appResource)
|
||||
|
||||
// If no Space Information no need to check org.
|
||||
if app.SpaceGuid != "" {
|
||||
//Getting Spaces Resource
|
||||
space, err := app.Space()
|
||||
if err != nil {
|
||||
errors.Wrap(err, "Unable to get the Space for the apps "+app.Name)
|
||||
} else {
|
||||
app.SpaceData.Entity = space
|
||||
|
||||
}
|
||||
|
||||
//Getting orgResource
|
||||
org, err := app.SpaceData.Entity.Org()
|
||||
if err != nil {
|
||||
errors.Wrap(err, "Unable to get the Org for the apps "+app.Name)
|
||||
} else {
|
||||
app.SpaceData.Entity.OrgData.Entity = org
|
||||
}
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListApps() ([]App, error) {
|
||||
q := url.Values{}
|
||||
q.Set("inline-relations-depth", "2")
|
||||
return c.ListAppsByQuery(q)
|
||||
}
|
||||
|
||||
func (c *Client) ListAppsByRoute(routeGuid string) ([]App, error) {
|
||||
return c.listApps(fmt.Sprintf("/v2/routes/%s/apps", routeGuid), -1)
|
||||
}
|
||||
|
||||
func (c *Client) listApps(requestUrl string, totalPages int) ([]App, error) {
|
||||
pages := 0
|
||||
apps := []App{}
|
||||
for {
|
||||
var appResp AppResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting apps")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading app request")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &appResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling app")
|
||||
}
|
||||
for _, app := range appResp.Resources {
|
||||
apps = append(apps, c.mergeAppResource(app))
|
||||
}
|
||||
|
||||
requestUrl = appResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
|
||||
pages += 1
|
||||
if totalPages > 0 && pages >= totalPages {
|
||||
break
|
||||
}
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetAppInstances(guid string) (map[string]AppInstance, error) {
|
||||
var appInstances map[string]AppInstance
|
||||
|
||||
requestURL := fmt.Sprintf("/v2/apps/%s/instances", guid)
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting app instances")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading app instances")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &appInstances)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling app instances")
|
||||
}
|
||||
return appInstances, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetAppEnv(guid string) (AppEnv, error) {
|
||||
var appEnv AppEnv
|
||||
|
||||
requestURL := fmt.Sprintf("/v2/apps/%s/env", guid)
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return appEnv, errors.Wrap(err, "Error requesting app env")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return appEnv, errors.Wrap(err, "Error reading app env")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &appEnv)
|
||||
if err != nil {
|
||||
return appEnv, errors.Wrap(err, "Error unmarshalling app env")
|
||||
}
|
||||
return appEnv, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetAppRoutes(guid string) ([]Route, error) {
|
||||
return c.fetchRoutes(fmt.Sprintf("/v2/apps/%s/routes", guid))
|
||||
}
|
||||
|
||||
func (c *Client) GetAppStats(guid string) (map[string]AppStats, error) {
|
||||
var appStats map[string]AppStats
|
||||
|
||||
requestURL := fmt.Sprintf("/v2/apps/%s/stats", guid)
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting app stats")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading app stats")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &appStats)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling app stats")
|
||||
}
|
||||
return appStats, nil
|
||||
}
|
||||
|
||||
func (c *Client) KillAppInstance(guid string, index string) error {
|
||||
requestURL := fmt.Sprintf("/v2/apps/%s/instances/%s", guid, index)
|
||||
r := c.NewRequest("DELETE", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error stopping app %s at index %s", guid, index)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 204 {
|
||||
return errors.Wrapf(err, "Error stopping app %s at index %s", guid, index)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetAppByGuid(guid string) (App, error) {
|
||||
var appResource AppResource
|
||||
r := c.NewRequest("GET", "/v2/apps/"+guid+"?inline-relations-depth=2")
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrap(err, "Error requesting apps")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrap(err, "Error reading app response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &appResource)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrap(err, "Error unmarshalling app")
|
||||
}
|
||||
return c.mergeAppResource(appResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) AppByGuid(guid string) (App, error) {
|
||||
return c.GetAppByGuid(guid)
|
||||
}
|
||||
|
||||
//AppByName takes an appName, and GUIDs for a space and org, and performs
|
||||
// the API lookup with those query parameters set to return you the desired
|
||||
// App object.
|
||||
func (c *Client) AppByName(appName, spaceGuid, orgGuid string) (app App, err error) {
|
||||
query := url.Values{}
|
||||
query.Add("q", fmt.Sprintf("organization_guid:%s", orgGuid))
|
||||
query.Add("q", fmt.Sprintf("space_guid:%s", spaceGuid))
|
||||
query.Add("q", fmt.Sprintf("name:%s", appName))
|
||||
apps, err := c.ListAppsByQuery(query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(apps) == 0 {
|
||||
err = fmt.Errorf("No app found with name: `%s` in space with GUID `%s` and org with GUID `%s`", appName, spaceGuid, orgGuid)
|
||||
return
|
||||
}
|
||||
app = apps[0]
|
||||
return
|
||||
}
|
||||
|
||||
// UploadAppBits uploads the application's contents
|
||||
func (c *Client) UploadAppBits(file io.Reader, appGUID string) error {
|
||||
requestFile, err := ioutil.TempFile("", "requests")
|
||||
|
||||
defer func() {
|
||||
requestFile.Close()
|
||||
os.Remove(requestFile.Name())
|
||||
}()
|
||||
|
||||
writer := multipart.NewWriter(requestFile)
|
||||
err = writer.WriteField("resources", "[]")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
|
||||
}
|
||||
|
||||
part, err := writer.CreateFormFile("application", "application.zip")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
|
||||
}
|
||||
|
||||
_, err = io.Copy(part, file)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits, failed to copy all bytes", appGUID)
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits, failed to close multipart writer", appGUID)
|
||||
}
|
||||
|
||||
requestFile.Seek(0, 0)
|
||||
fileStats, err := requestFile.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits, failed to get temp file stats", appGUID)
|
||||
}
|
||||
|
||||
requestURL := fmt.Sprintf("/v2/apps/%s/bits", appGUID)
|
||||
r := c.NewRequestWithBody("PUT", requestURL, requestFile)
|
||||
req, err := r.toHTTP()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
|
||||
}
|
||||
|
||||
req.ContentLength = fileStats.Size()
|
||||
contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits", appGUID)
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return errors.Wrapf(err, "Error uploading app %s bits, response code: %d", appGUID, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAppBits downloads the application's bits as a tar file
|
||||
func (c *Client) GetAppBits(guid string) (io.ReadCloser, error) {
|
||||
requestURL := fmt.Sprintf("/v2/apps/%s/download", guid)
|
||||
req := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequestWithoutRedirects(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error downloading app %s bits, API request failed", guid)
|
||||
}
|
||||
if isResponseRedirect(resp) {
|
||||
// directly download the bits from blobstore using a non cloud controller transport
|
||||
// some blobstores will return a 400 if an Authorization header is sent
|
||||
blobStoreLocation := resp.Header.Get("Location")
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.Config.SkipSslValidation},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
resp, err = client.Get(blobStoreLocation)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error downloading app %s bits from blobstore", guid)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "Error downloading app %s bits, expected redirect to blobstore", guid)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// CreateApp creates a new empty application that still needs it's
|
||||
// app bit uploaded and to be started
|
||||
func (c *Client) CreateApp(req AppCreateRequest) (App, error) {
|
||||
var appResp AppResource
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return App{}, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/apps", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrapf(err, "Error creating app %s", req.Name)
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return App{}, errors.Wrapf(err, "Error creating app %s, response code: %d", req.Name, resp.StatusCode)
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return App{}, errors.Wrapf(err, "Error reading app %s http response body", req.Name)
|
||||
}
|
||||
err = json.Unmarshal(resBody, &appResp)
|
||||
if err != nil {
|
||||
return App{}, errors.Wrapf(err, "Error deserializing app %s response", req.Name)
|
||||
}
|
||||
return c.mergeAppResource(appResp), nil
|
||||
}
|
||||
|
||||
func (c *Client) StartApp(guid string) error {
|
||||
startRequest := strings.NewReader(`{ "state": "STARTED" }`)
|
||||
resp, err := c.DoRequest(c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/apps/%s", guid), startRequest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error starting app %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) StopApp(guid string) error {
|
||||
stopRequest := strings.NewReader(`{ "state": "STOPPED" }`)
|
||||
resp, err := c.DoRequest(c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/apps/%s", guid), stopRequest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error stopping app %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteApp(guid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/apps/%s", guid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting app %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeAppResource(app AppResource) App {
|
||||
app.Entity.Guid = app.Meta.Guid
|
||||
app.Entity.CreatedAt = app.Meta.CreatedAt
|
||||
app.Entity.UpdatedAt = app.Meta.UpdatedAt
|
||||
app.Entity.SpaceData.Entity.Guid = app.Entity.SpaceData.Meta.Guid
|
||||
app.Entity.SpaceData.Entity.OrgData.Entity.Guid = app.Entity.SpaceData.Entity.OrgData.Meta.Guid
|
||||
app.Entity.c = c
|
||||
return app.Entity
|
||||
}
|
||||
|
||||
func isResponseRedirect(res *http.Response) bool {
|
||||
switch res.StatusCode {
|
||||
case http.StatusTemporaryRedirect, http.StatusPermanentRedirect, http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
247
vendor/github.com/cloudfoundry-community/go-cfclient/buildpacks.go
generated
vendored
Normal file
247
vendor/github.com/cloudfoundry-community/go-cfclient/buildpacks.go
generated
vendored
Normal file
|
@ -0,0 +1,247 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.cloudfoundry.org/gofileutils/fileutils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type BuildpackResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []BuildpackResource `json:"resources"`
|
||||
}
|
||||
|
||||
type BuildpackResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Buildpack `json:"entity"`
|
||||
}
|
||||
|
||||
type Buildpack struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Locked bool `json:"locked"`
|
||||
Position int `json:"position"`
|
||||
Filename string `json:"filename"`
|
||||
Stack string `json:"stack"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type BuildpackRequest struct {
|
||||
// These are all pointers to the values so that we can tell
|
||||
// whether people wanted position 0, or enable/unlock values,
|
||||
// vs whether they didn't specify them and want them unchanged/default.
|
||||
Name *string `json:"name,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
Locked *bool `json:"locked,omitempty"`
|
||||
Position *int `json:"position,omitempty"`
|
||||
Stack *string `json:"stack,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateBuildpack(bpr *BuildpackRequest) (*Buildpack, error) {
|
||||
if bpr.Name == nil || *bpr.Name == "" {
|
||||
return nil, errors.New("Unable to create a buidlpack with no name")
|
||||
}
|
||||
requestUrl := "/v2/buildpacks"
|
||||
req := c.NewRequest("POST", requestUrl)
|
||||
req.obj = bpr
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating buildpack:")
|
||||
}
|
||||
bp, err := c.handleBuildpackResp(resp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating buildpack:")
|
||||
}
|
||||
return &bp, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListBuildpacks() ([]Buildpack, error) {
|
||||
var buildpacks []Buildpack
|
||||
requestUrl := "/v2/buildpacks"
|
||||
for {
|
||||
buildpackResp, err := c.getBuildpackResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []Buildpack{}, err
|
||||
}
|
||||
for _, buildpack := range buildpackResp.Resources {
|
||||
buildpacks = append(buildpacks, c.mergeBuildpackResource(buildpack))
|
||||
}
|
||||
requestUrl = buildpackResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buildpacks, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteBuildpack(guid string, async bool) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/buildpacks/%s?async=%t", guid, async)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (async && (resp.StatusCode != http.StatusAccepted)) || (!async && (resp.StatusCode != http.StatusNoContent)) {
|
||||
return errors.Wrapf(err, "Error deleting buildpack %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getBuildpackResponse(requestUrl string) (BuildpackResponse, error) {
|
||||
var buildpackResp BuildpackResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return BuildpackResponse{}, errors.Wrap(err, "Error requesting buildpacks")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return BuildpackResponse{}, errors.Wrap(err, "Error reading buildpack request")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &buildpackResp)
|
||||
if err != nil {
|
||||
return BuildpackResponse{}, errors.Wrap(err, "Error unmarshalling buildpack")
|
||||
}
|
||||
return buildpackResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeBuildpackResource(buildpack BuildpackResource) Buildpack {
|
||||
buildpack.Entity.Guid = buildpack.Meta.Guid
|
||||
buildpack.Entity.CreatedAt = buildpack.Meta.CreatedAt
|
||||
buildpack.Entity.UpdatedAt = buildpack.Meta.UpdatedAt
|
||||
buildpack.Entity.c = c
|
||||
return buildpack.Entity
|
||||
}
|
||||
|
||||
func (c *Client) GetBuildpackByGuid(buildpackGUID string) (Buildpack, error) {
|
||||
requestUrl := fmt.Sprintf("/v2/buildpacks/%s", buildpackGUID)
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Buildpack{}, errors.Wrap(err, "Error requesting buildpack info")
|
||||
}
|
||||
return c.handleBuildpackResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) handleBuildpackResp(resp *http.Response) (Buildpack, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return Buildpack{}, err
|
||||
}
|
||||
var buildpackResource BuildpackResource
|
||||
if err := json.Unmarshal(body, &buildpackResource); err != nil {
|
||||
return Buildpack{}, err
|
||||
}
|
||||
return c.mergeBuildpackResource(buildpackResource), nil
|
||||
}
|
||||
|
||||
func (b *Buildpack) Upload(file io.Reader, fileName string) error {
|
||||
var capturedErr error
|
||||
fileutils.TempFile("requests", func(requestFile *os.File, err error) {
|
||||
if err != nil {
|
||||
capturedErr = err
|
||||
return
|
||||
}
|
||||
writer := multipart.NewWriter(requestFile)
|
||||
part, err := writer.CreateFormFile("buildpack", fileName)
|
||||
|
||||
if err != nil {
|
||||
_ = writer.Close()
|
||||
capturedErr = err
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.Copy(part, file)
|
||||
if err != nil {
|
||||
capturedErr = fmt.Errorf("Error creating upload: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
capturedErr = err
|
||||
return
|
||||
}
|
||||
|
||||
requestFile.Seek(0, 0)
|
||||
fileStats, err := requestFile.Stat()
|
||||
if err != nil {
|
||||
capturedErr = fmt.Errorf("Error getting file info: %s", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", fmt.Sprintf("%s/v2/buildpacks/%s/bits", b.c.Config.ApiAddress, b.Guid), requestFile)
|
||||
if err != nil {
|
||||
capturedErr = err
|
||||
return
|
||||
}
|
||||
|
||||
req.ContentLength = fileStats.Size()
|
||||
contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary())
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
resp, err := b.c.Do(req) //client.Do() handles the HTTP status code checking for us
|
||||
if err != nil {
|
||||
capturedErr = err
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
})
|
||||
|
||||
return errors.Wrap(capturedErr, "Error uploading buildpack:")
|
||||
}
|
||||
|
||||
func (b *Buildpack) Update(bpr *BuildpackRequest) error {
|
||||
requestUrl := fmt.Sprintf("/v2/buildpacks/%s", b.Guid)
|
||||
req := b.c.NewRequest("PUT", requestUrl)
|
||||
req.obj = bpr
|
||||
resp, err := b.c.DoRequest(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error updating buildpack:")
|
||||
}
|
||||
newBp, err := b.c.handleBuildpackResp(resp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error updating buildpack:")
|
||||
}
|
||||
b.Name = newBp.Name
|
||||
b.Locked = newBp.Locked
|
||||
b.Enabled = newBp.Enabled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bpr *BuildpackRequest) Lock() {
|
||||
b := true
|
||||
bpr.Locked = &b
|
||||
}
|
||||
func (bpr *BuildpackRequest) Unlock() {
|
||||
b := false
|
||||
bpr.Locked = &b
|
||||
}
|
||||
func (bpr *BuildpackRequest) Enable() {
|
||||
b := true
|
||||
bpr.Enabled = &b
|
||||
}
|
||||
func (bpr *BuildpackRequest) Disable() {
|
||||
b := false
|
||||
bpr.Enabled = &b
|
||||
}
|
||||
func (bpr *BuildpackRequest) SetPosition(i int) {
|
||||
bpr.Position = &i
|
||||
}
|
||||
func (bpr *BuildpackRequest) SetName(s string) {
|
||||
bpr.Name = &s
|
||||
}
|
||||
func (bpr *BuildpackRequest) SetStack(s string) {
|
||||
bpr.Stack = &s
|
||||
}
|
3171
vendor/github.com/cloudfoundry-community/go-cfclient/cf_error.go
generated
vendored
Normal file
3171
vendor/github.com/cloudfoundry-community/go-cfclient/cf_error.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,407 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
)
|
||||
|
||||
//Client used to communicate with Cloud Foundry
|
||||
type Client struct {
|
||||
Config Config
|
||||
Endpoint Endpoint
|
||||
}
|
||||
|
||||
type Endpoint struct {
|
||||
DopplerEndpoint string `json:"doppler_logging_endpoint"`
|
||||
LoggingEndpoint string `json:"logging_endpoint"`
|
||||
AuthEndpoint string `json:"authorization_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
}
|
||||
|
||||
//Config is used to configure the creation of a client
|
||||
type Config struct {
|
||||
ApiAddress string `json:"api_url"`
|
||||
Username string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
SkipSslValidation bool `json:"skip_ssl_validation"`
|
||||
HttpClient *http.Client
|
||||
Token string `json:"auth_token"`
|
||||
TokenSource oauth2.TokenSource
|
||||
tokenSourceDeadline *time.Time
|
||||
UserAgent string `json:"user_agent"`
|
||||
}
|
||||
|
||||
// Request is used to help build up a request
|
||||
type Request struct {
|
||||
method string
|
||||
url string
|
||||
params url.Values
|
||||
body io.Reader
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
//DefaultConfig configuration for client
|
||||
//Keep LoginAdress for backward compatibility
|
||||
//Need to be remove in close future
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
ApiAddress: "http://api.bosh-lite.com",
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
Token: "",
|
||||
SkipSslValidation: false,
|
||||
HttpClient: http.DefaultClient,
|
||||
UserAgent: "Go-CF-client/1.1",
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultEndpoint() *Endpoint {
|
||||
return &Endpoint{
|
||||
DopplerEndpoint: "wss://doppler.10.244.0.34.xip.io:443",
|
||||
LoggingEndpoint: "wss://loggregator.10.244.0.34.xip.io:443",
|
||||
TokenEndpoint: "https://uaa.10.244.0.34.xip.io",
|
||||
AuthEndpoint: "https://login.10.244.0.34.xip.io",
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient returns a new client
|
||||
func NewClient(config *Config) (client *Client, err error) {
|
||||
// bootstrap the config
|
||||
defConfig := DefaultConfig()
|
||||
|
||||
if len(config.ApiAddress) == 0 {
|
||||
config.ApiAddress = defConfig.ApiAddress
|
||||
}
|
||||
|
||||
if len(config.Username) == 0 {
|
||||
config.Username = defConfig.Username
|
||||
}
|
||||
|
||||
if len(config.Password) == 0 {
|
||||
config.Password = defConfig.Password
|
||||
}
|
||||
|
||||
if len(config.Token) == 0 {
|
||||
config.Token = defConfig.Token
|
||||
}
|
||||
|
||||
if len(config.UserAgent) == 0 {
|
||||
config.UserAgent = defConfig.UserAgent
|
||||
}
|
||||
|
||||
if config.HttpClient == nil {
|
||||
config.HttpClient = defConfig.HttpClient
|
||||
}
|
||||
|
||||
if config.HttpClient.Transport == nil {
|
||||
config.HttpClient.Transport = shallowDefaultTransport()
|
||||
}
|
||||
|
||||
var tp *http.Transport
|
||||
|
||||
switch t := config.HttpClient.Transport.(type) {
|
||||
case *http.Transport:
|
||||
tp = t
|
||||
case *oauth2.Transport:
|
||||
if bt, ok := t.Base.(*http.Transport); ok {
|
||||
tp = bt
|
||||
}
|
||||
}
|
||||
|
||||
if tp != nil {
|
||||
if tp.TLSClientConfig == nil {
|
||||
tp.TLSClientConfig = &tls.Config{}
|
||||
}
|
||||
tp.TLSClientConfig.InsecureSkipVerify = config.SkipSslValidation
|
||||
}
|
||||
|
||||
config.ApiAddress = strings.TrimRight(config.ApiAddress, "/")
|
||||
|
||||
client = &Client{
|
||||
Config: *config,
|
||||
}
|
||||
|
||||
if err := client.refreshEndpoint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func shallowDefaultTransport() *http.Transport {
|
||||
defaultTransport := http.DefaultTransport.(*http.Transport)
|
||||
return &http.Transport{
|
||||
Proxy: defaultTransport.Proxy,
|
||||
TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout,
|
||||
ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func getUserAuth(ctx context.Context, config Config, endpoint *Endpoint) (Config, error) {
|
||||
authConfig := &oauth2.Config{
|
||||
ClientID: "cf",
|
||||
Scopes: []string{""},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: endpoint.AuthEndpoint + "/oauth/auth",
|
||||
TokenURL: endpoint.TokenEndpoint + "/oauth/token",
|
||||
},
|
||||
}
|
||||
|
||||
token, err := authConfig.PasswordCredentialsToken(ctx, config.Username, config.Password)
|
||||
if err != nil {
|
||||
return config, errors.Wrap(err, "Error getting token")
|
||||
}
|
||||
|
||||
config.tokenSourceDeadline = &token.Expiry
|
||||
config.TokenSource = authConfig.TokenSource(ctx, token)
|
||||
config.HttpClient = oauth2.NewClient(ctx, config.TokenSource)
|
||||
|
||||
return config, err
|
||||
}
|
||||
|
||||
func getClientAuth(ctx context.Context, config Config, endpoint *Endpoint) Config {
|
||||
authConfig := &clientcredentials.Config{
|
||||
ClientID: config.ClientID,
|
||||
ClientSecret: config.ClientSecret,
|
||||
TokenURL: endpoint.TokenEndpoint + "/oauth/token",
|
||||
}
|
||||
|
||||
config.TokenSource = authConfig.TokenSource(ctx)
|
||||
config.HttpClient = authConfig.Client(ctx)
|
||||
return config
|
||||
}
|
||||
|
||||
// getUserTokenAuth initializes client credentials from existing bearer token.
|
||||
func getUserTokenAuth(ctx context.Context, config Config, endpoint *Endpoint) Config {
|
||||
authConfig := &oauth2.Config{
|
||||
ClientID: "cf",
|
||||
Scopes: []string{""},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: endpoint.AuthEndpoint + "/oauth/auth",
|
||||
TokenURL: endpoint.TokenEndpoint + "/oauth/token",
|
||||
},
|
||||
}
|
||||
|
||||
// Token is expected to have no "bearer" prefix
|
||||
token := &oauth2.Token{
|
||||
AccessToken: config.Token,
|
||||
TokenType: "Bearer"}
|
||||
|
||||
config.TokenSource = authConfig.TokenSource(ctx, token)
|
||||
config.HttpClient = oauth2.NewClient(ctx, config.TokenSource)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getInfo(api string, httpClient *http.Client) (*Endpoint, error) {
|
||||
var endpoint Endpoint
|
||||
|
||||
if api == "" {
|
||||
return DefaultEndpoint(), nil
|
||||
}
|
||||
|
||||
resp, err := httpClient.Get(api + "/v2/info")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = decodeBody(resp, &endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &endpoint, err
|
||||
}
|
||||
|
||||
// NewRequest is used to create a new Request
|
||||
func (c *Client) NewRequest(method, path string) *Request {
|
||||
r := &Request{
|
||||
method: method,
|
||||
url: c.Config.ApiAddress + path,
|
||||
params: make(map[string][]string),
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// NewRequestWithBody is used to create a new request with
|
||||
// arbigtrary body io.Reader.
|
||||
func (c *Client) NewRequestWithBody(method, path string, body io.Reader) *Request {
|
||||
r := c.NewRequest(method, path)
|
||||
|
||||
// Set request body
|
||||
r.body = body
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// DoRequest runs a request with our client
|
||||
func (c *Client) DoRequest(r *Request) (*http.Response, error) {
|
||||
req, err := r.toHTTP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// DoRequestWithoutRedirects executes the request without following redirects
|
||||
func (c *Client) DoRequestWithoutRedirects(r *Request) (*http.Response, error) {
|
||||
prevCheckRedirect := c.Config.HttpClient.CheckRedirect
|
||||
c.Config.HttpClient.CheckRedirect = func(httpReq *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
defer func() {
|
||||
c.Config.HttpClient.CheckRedirect = prevCheckRedirect
|
||||
}()
|
||||
return c.DoRequest(r)
|
||||
}
|
||||
|
||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("User-Agent", c.Config.UserAgent)
|
||||
if req.Body != nil && req.Header.Get("Content-type") == "" {
|
||||
req.Header.Set("Content-type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.Config.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return c.handleError(resp)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) handleError(resp *http.Response) (*http.Response, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return resp, CloudFoundryHTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Status: resp.Status,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Unmarshal V2 error response
|
||||
if strings.HasPrefix(resp.Request.URL.Path, "/v2/") {
|
||||
var cfErr CloudFoundryError
|
||||
if err := json.Unmarshal(body, &cfErr); err != nil {
|
||||
return resp, CloudFoundryHTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Status: resp.Status,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
return nil, cfErr
|
||||
}
|
||||
|
||||
// Unmarshal a V3 error response and convert it into a V2 model
|
||||
var cfErrorsV3 CloudFoundryErrorsV3
|
||||
if err := json.Unmarshal(body, &cfErrorsV3); err != nil {
|
||||
return resp, CloudFoundryHTTPError{
|
||||
StatusCode: resp.StatusCode,
|
||||
Status: resp.Status,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
return nil, NewCloudFoundryErrorFromV3Errors(cfErrorsV3)
|
||||
}
|
||||
|
||||
func (c *Client) refreshEndpoint() error {
|
||||
// we want to keep the Timeout value from config.HttpClient
|
||||
timeout := c.Config.HttpClient.Timeout
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.Config.HttpClient)
|
||||
|
||||
endpoint, err := getInfo(c.Config.ApiAddress, oauth2.NewClient(ctx, nil))
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not get api /v2/info")
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.Config.Token != "":
|
||||
c.Config = getUserTokenAuth(ctx, c.Config, endpoint)
|
||||
case c.Config.ClientID != "":
|
||||
c.Config = getClientAuth(ctx, c.Config, endpoint)
|
||||
default:
|
||||
c.Config, err = getUserAuth(ctx, c.Config, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// make sure original Timeout value will be used
|
||||
if c.Config.HttpClient.Timeout != timeout {
|
||||
c.Config.HttpClient.Timeout = timeout
|
||||
}
|
||||
|
||||
c.Endpoint = *endpoint
|
||||
return nil
|
||||
}
|
||||
|
||||
// toHTTP converts the request to an HTTP Request
|
||||
func (r *Request) toHTTP() (*http.Request, error) {
|
||||
|
||||
// Check if we should encode the body
|
||||
if r.body == nil && r.obj != nil {
|
||||
b, err := encodeBody(r.obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.body = b
|
||||
}
|
||||
|
||||
// Create the HTTP Request
|
||||
return http.NewRequest(r.method, r.url, r.body)
|
||||
}
|
||||
|
||||
// decodeBody is used to JSON decode a body
|
||||
func decodeBody(resp *http.Response, out interface{}) error {
|
||||
defer resp.Body.Close()
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
return dec.Decode(out)
|
||||
}
|
||||
|
||||
// encodeBody is used to encode a request body
|
||||
func encodeBody(obj interface{}) (io.Reader, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := json.NewEncoder(buf)
|
||||
if err := enc.Encode(obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetToken() (string, error) {
|
||||
if c.Config.tokenSourceDeadline != nil && c.Config.tokenSourceDeadline.Before(time.Now()) {
|
||||
if err := c.refreshEndpoint(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
token, err := c.Config.TokenSource.Token()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error getting bearer token")
|
||||
}
|
||||
return "bearer " + token.AccessToken, nil
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type DomainsResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []DomainResource `json:"resources"`
|
||||
}
|
||||
|
||||
type SharedDomainsResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []SharedDomainResource `json:"resources"`
|
||||
}
|
||||
|
||||
type DomainResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Domain `json:"entity"`
|
||||
}
|
||||
|
||||
type SharedDomainResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity SharedDomain `json:"entity"`
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
OwningOrganizationGuid string `json:"owning_organization_guid"`
|
||||
OwningOrganizationUrl string `json:"owning_organization_url"`
|
||||
SharedOrganizationsUrl string `json:"shared_organizations_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type SharedDomain struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
RouterGroupGuid string `json:"router_group_guid"`
|
||||
RouterGroupType string `json:"router_group_type"`
|
||||
Internal bool `json:"internal"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListDomainsByQuery(query url.Values) ([]Domain, error) {
|
||||
var domains []Domain
|
||||
requestUrl := "/v2/private_domains?" + query.Encode()
|
||||
for {
|
||||
var domainResp DomainsResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting domains")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading domains request")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &domainResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling domains")
|
||||
}
|
||||
for _, domain := range domainResp.Resources {
|
||||
domain.Entity.Guid = domain.Meta.Guid
|
||||
domain.Entity.CreatedAt = domain.Meta.CreatedAt
|
||||
domain.Entity.UpdatedAt = domain.Meta.UpdatedAt
|
||||
domain.Entity.c = c
|
||||
domains = append(domains, domain.Entity)
|
||||
}
|
||||
requestUrl = domainResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListDomains() ([]Domain, error) {
|
||||
return c.ListDomainsByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) ListSharedDomainsByQuery(query url.Values) ([]SharedDomain, error) {
|
||||
var domains []SharedDomain
|
||||
requestUrl := "/v2/shared_domains?" + query.Encode()
|
||||
for {
|
||||
var domainResp SharedDomainsResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting shared domains")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading shared domains request")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &domainResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling shared domains")
|
||||
}
|
||||
for _, domain := range domainResp.Resources {
|
||||
domain.Entity.Guid = domain.Meta.Guid
|
||||
domain.Entity.CreatedAt = domain.Meta.CreatedAt
|
||||
domain.Entity.UpdatedAt = domain.Meta.UpdatedAt
|
||||
domain.Entity.c = c
|
||||
domains = append(domains, domain.Entity)
|
||||
}
|
||||
requestUrl = domainResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListSharedDomains() ([]SharedDomain, error) {
|
||||
return c.ListSharedDomainsByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetSharedDomainByGuid(guid string) (SharedDomain, error) {
|
||||
r := c.NewRequest("GET", "/v2/shared_domains/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return SharedDomain{}, errors.Wrap(err, "Error requesting shared domain")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
retval, err := c.handleSharedDomainResp(resp)
|
||||
return *retval, err
|
||||
}
|
||||
|
||||
func (c *Client) CreateSharedDomain(name string, internal bool, router_group_guid string) (*SharedDomain, error) {
|
||||
req := c.NewRequest("POST", "/v2/shared_domains")
|
||||
params := map[string]interface{}{
|
||||
"name": name,
|
||||
"internal": internal,
|
||||
}
|
||||
|
||||
if strings.TrimSpace(router_group_guid) != "" {
|
||||
params["router_group_guid"] = router_group_guid
|
||||
}
|
||||
|
||||
req.obj = params
|
||||
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, errors.Wrapf(err, "Error creating shared domain %s, response code: %d", name, resp.StatusCode)
|
||||
}
|
||||
return c.handleSharedDomainResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteSharedDomain(guid string, async bool) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/shared_domains/%s?async=%t", guid, async)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (async && (resp.StatusCode != http.StatusAccepted)) || (!async && (resp.StatusCode != http.StatusNoContent)) {
|
||||
return errors.Wrapf(err, "Error deleting organization %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetDomainByName(name string) (Domain, error) {
|
||||
q := url.Values{}
|
||||
q.Set("q", "name:"+name)
|
||||
domains, err := c.ListDomainsByQuery(q)
|
||||
if err != nil {
|
||||
return Domain{}, errors.Wrapf(err, "Error during domain lookup %s", name)
|
||||
}
|
||||
if len(domains) == 0 {
|
||||
return Domain{}, fmt.Errorf("Unable to find domain %s", name)
|
||||
}
|
||||
return domains[0], nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSharedDomainByName(name string) (SharedDomain, error) {
|
||||
q := url.Values{}
|
||||
q.Set("q", "name:"+name)
|
||||
domains, err := c.ListSharedDomainsByQuery(q)
|
||||
if err != nil {
|
||||
return SharedDomain{}, errors.Wrapf(err, "Error during shared domain lookup %s", name)
|
||||
}
|
||||
if len(domains) == 0 {
|
||||
return SharedDomain{}, fmt.Errorf("Unable to find shared domain %s", name)
|
||||
}
|
||||
return domains[0], nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateDomain(name, orgGuid string) (*Domain, error) {
|
||||
req := c.NewRequest("POST", "/v2/private_domains")
|
||||
req.obj = map[string]interface{}{
|
||||
"name": name,
|
||||
"owning_organization_guid": orgGuid,
|
||||
}
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, errors.Wrapf(err, "Error creating domain %s, response code: %d", name, resp.StatusCode)
|
||||
}
|
||||
return c.handleDomainResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteDomain(guid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/private_domains/%s", guid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting domain %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *Client) handleDomainResp(resp *http.Response) (*Domain, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var domainResource DomainResource
|
||||
err = json.Unmarshal(body, &domainResource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.mergeDomainResource(domainResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) handleSharedDomainResp(resp *http.Response) (*SharedDomain, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var domainResource SharedDomainResource
|
||||
err = json.Unmarshal(body, &domainResource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.mergeSharedDomainResource(domainResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) getDomainsResponse(requestUrl string) (DomainsResponse, error) {
|
||||
var domainResp DomainsResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return DomainsResponse{}, errors.Wrap(err, "Error requesting domains")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return DomainsResponse{}, errors.Wrap(err, "Error reading domains request")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &domainResp)
|
||||
if err != nil {
|
||||
return DomainsResponse{}, errors.Wrap(err, "Error unmarshalling org")
|
||||
}
|
||||
return domainResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeDomainResource(domainResource DomainResource) *Domain {
|
||||
domainResource.Entity.Guid = domainResource.Meta.Guid
|
||||
domainResource.Entity.c = c
|
||||
return &domainResource.Entity
|
||||
}
|
||||
|
||||
func (c *Client) mergeSharedDomainResource(domainResource SharedDomainResource) *SharedDomain {
|
||||
domainResource.Entity.Guid = domainResource.Meta.Guid
|
||||
domainResource.Entity.c = c
|
||||
return &domainResource.Entity
|
||||
}
|
59
vendor/github.com/cloudfoundry-community/go-cfclient/environmentvariablegroups.go
generated
vendored
Normal file
59
vendor/github.com/cloudfoundry-community/go-cfclient/environmentvariablegroups.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type EnvironmentVariableGroup map[string]interface{}
|
||||
|
||||
func (c *Client) GetRunningEnvironmentVariableGroup() (EnvironmentVariableGroup, error) {
|
||||
return c.getEnvironmentVariableGroup(true)
|
||||
}
|
||||
|
||||
func (c *Client) GetStagingEnvironmentVariableGroup() (EnvironmentVariableGroup, error) {
|
||||
return c.getEnvironmentVariableGroup(false)
|
||||
}
|
||||
|
||||
func (c *Client) getEnvironmentVariableGroup(running bool) (EnvironmentVariableGroup, error) {
|
||||
evgType := "staging"
|
||||
if running {
|
||||
evgType = "running"
|
||||
}
|
||||
|
||||
req := c.NewRequest("GET", fmt.Sprintf("/v2/config/environment_variable_groups/%s", evgType))
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
evg := EnvironmentVariableGroup{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&evg)
|
||||
return evg, err
|
||||
}
|
||||
|
||||
func (c *Client) SetRunningEnvironmentVariableGroup(evg EnvironmentVariableGroup) error {
|
||||
return c.setEnvironmentVariableGroup(evg, true)
|
||||
}
|
||||
|
||||
func (c *Client) SetStagingEnvironmentVariableGroup(evg EnvironmentVariableGroup) error {
|
||||
return c.setEnvironmentVariableGroup(evg, false)
|
||||
}
|
||||
|
||||
func (c *Client) setEnvironmentVariableGroup(evg EnvironmentVariableGroup, running bool) error {
|
||||
evgType := "staging"
|
||||
if running {
|
||||
evgType = "running"
|
||||
}
|
||||
|
||||
marshalled, err := json.Marshal(evg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/config/environment_variable_groups/%s", evgType), bytes.NewBuffer(marshalled))
|
||||
_, err = c.DoRequest(req)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package cfclient
|
||||
|
||||
//go:generate go run gen_error.go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CloudFoundryError struct {
|
||||
Code int `json:"code"`
|
||||
ErrorCode string `json:"error_code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type CloudFoundryErrorsV3 struct {
|
||||
Errors []CloudFoundryErrorV3 `json:"errors"`
|
||||
}
|
||||
|
||||
type CloudFoundryErrorV3 struct {
|
||||
Code int `json:"code"`
|
||||
Title string `json:"title"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
// CF APIs v3 can return multiple errors, we take the first one and convert it into a V2 model
|
||||
func NewCloudFoundryErrorFromV3Errors(cfErrorsV3 CloudFoundryErrorsV3) CloudFoundryError {
|
||||
if len(cfErrorsV3.Errors) == 0 {
|
||||
return CloudFoundryError{
|
||||
0,
|
||||
"GO-Client-No-Errors",
|
||||
"No Errors in response from V3",
|
||||
}
|
||||
}
|
||||
|
||||
return CloudFoundryError{
|
||||
cfErrorsV3.Errors[0].Code,
|
||||
cfErrorsV3.Errors[0].Title,
|
||||
cfErrorsV3.Errors[0].Detail,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfErr CloudFoundryError) Error() string {
|
||||
return fmt.Sprintf("cfclient error (%s|%d): %s", cfErr.ErrorCode, cfErr.Code, cfErr.Description)
|
||||
}
|
||||
|
||||
type CloudFoundryHTTPError struct {
|
||||
StatusCode int
|
||||
Status string
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (e CloudFoundryHTTPError) Error() string {
|
||||
return fmt.Sprintf("cfclient: HTTP error (%d): %s", e.StatusCode, e.Status)
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// EventsResponse is a type that wraps a collection of event resources.
|
||||
type EventsResponse struct {
|
||||
TotalResults int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextURL string `json:"next_url"`
|
||||
Resources []EventResource `json:"resources"`
|
||||
}
|
||||
|
||||
// EventResource is a type that contains metadata and the entity for an event.
|
||||
type EventResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Event `json:"entity"`
|
||||
}
|
||||
|
||||
// Event is a type that contains event data.
|
||||
type Event struct {
|
||||
GUID string `json:"guid"`
|
||||
Type string `json:"type"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Actor string `json:"actor"`
|
||||
ActorType string `json:"actor_type"`
|
||||
ActorName string `json:"actor_name"`
|
||||
ActorUsername string `json:"actor_username"`
|
||||
Actee string `json:"actee"`
|
||||
ActeeType string `json:"actee_type"`
|
||||
ActeeName string `json:"actee_name"`
|
||||
OrganizationGUID string `json:"organization_guid"`
|
||||
SpaceGUID string `json:"space_guid"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
// ListEventsByQuery lists all events matching the provided query.
|
||||
func (c *Client) ListEventsByQuery(query url.Values) ([]Event, error) {
|
||||
var events []Event
|
||||
requestURL := fmt.Sprintf("/v2/events?%s", query.Encode())
|
||||
for {
|
||||
var eventResp EventsResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error requesting events")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if err := json.NewDecoder(resp.Body).Decode(&eventResp); err != nil {
|
||||
return nil, errors.Wrap(err, "error unmarshaling events")
|
||||
}
|
||||
for _, e := range eventResp.Resources {
|
||||
e.Entity.GUID = e.Meta.Guid
|
||||
e.Entity.CreatedAt = e.Meta.CreatedAt
|
||||
e.Entity.c = c
|
||||
events = append(events, e.Entity)
|
||||
}
|
||||
requestURL = eventResp.NextURL
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// ListEvents lists all unfiltered events.
|
||||
func (c *Client) ListEvents() ([]Event, error) {
|
||||
return c.ListEventsByQuery(nil)
|
||||
}
|
||||
|
||||
// TotalEventsByQuery returns the number of events matching the provided query.
|
||||
func (c *Client) TotalEventsByQuery(query url.Values) (int, error) {
|
||||
r := c.NewRequest("GET", fmt.Sprintf("/v2/events?%s", query.Encode()))
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error requesting events")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var apiResp EventsResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
|
||||
return 0, errors.Wrap(err, "error unmarshaling events")
|
||||
}
|
||||
return apiResp.TotalResults, nil
|
||||
}
|
||||
|
||||
// TotalEvents returns the number of unfiltered events.
|
||||
func (c *Client) TotalEvents() (int, error) {
|
||||
return c.TotalEventsByQuery(nil)
|
||||
}
|
115
vendor/github.com/cloudfoundry-community/go-cfclient/gen_error.go
generated
vendored
Normal file
115
vendor/github.com/cloudfoundry-community/go-cfclient/gen_error.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type CFCode int
|
||||
type HTTPCode int
|
||||
|
||||
type Definition struct {
|
||||
CFCode `yaml:"-"`
|
||||
Name string `yaml:"name"`
|
||||
HTTPCode `yaml:"http_code"`
|
||||
Message string `yaml:"message"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
const url = "https://raw.githubusercontent.com/cloudfoundry/cloud_controller_ng/master/vendor/errors/v2.yml"
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var m map[CFCode]Definition
|
||||
|
||||
if err := yaml.Unmarshal(body, &m); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var definitions []Definition
|
||||
|
||||
for c, d := range m {
|
||||
d.CFCode = c
|
||||
definitions = append(definitions, d)
|
||||
}
|
||||
|
||||
sort.Slice(definitions, func(i, j int) bool {
|
||||
return definitions[i].CFCode < definitions[j].CFCode
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if err := packageTemplate.Execute(buf, struct {
|
||||
Timestamp time.Time
|
||||
Definitions []Definition
|
||||
}{
|
||||
Timestamp: time.Now(),
|
||||
Definitions: definitions,
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
dst, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Printf("%s", buf.Bytes())
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile("cf_error.go", dst, 0600); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// destutter ensures that s does not end in "Error".
|
||||
func destutter(s string) string {
|
||||
return strings.TrimSuffix(s, "Error")
|
||||
}
|
||||
|
||||
var packageTemplate = template.Must(template.New("").Funcs(template.FuncMap{
|
||||
"destutter": destutter,
|
||||
}).Parse(`
|
||||
package cfclient
|
||||
|
||||
// Code generated by go generate. DO NOT EDIT.
|
||||
// This file was generated by robots at
|
||||
// {{ .Timestamp }}
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
{{- range .Definitions }}
|
||||
{{$method := printf "Is%sError" (.Name | destutter) }}
|
||||
// {{ $method }} returns a boolean indicating whether
|
||||
// the error is known to report the Cloud Foundry error:
|
||||
// - Cloud Foundry code: {{ .CFCode }}
|
||||
// - HTTP code: {{ .HTTPCode }}
|
||||
// - message: {{ printf "%q" .Message }}
|
||||
func Is{{ .Name | destutter }}Error(err error) bool {
|
||||
cause := errors.Cause(err)
|
||||
cferr, ok := cause.(CloudFoundryError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return cferr.Code == {{ .CFCode }}
|
||||
}
|
||||
{{- end }}
|
||||
`))
|
|
@ -0,0 +1,20 @@
|
|||
module github.com/cloudfoundry-community/go-cfclient
|
||||
|
||||
require (
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11
|
||||
github.com/onsi/gomega v1.4.3
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470 // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
|
@ -0,0 +1,65 @@
|
|||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw=
|
||||
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=
|
||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470 h1:R0uuDVEvfDha2O6dfJRr4/5NBHKEbZhMPZmqOWpEkSo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmyWFrBXJ3PBy10xKMXK8=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1 h1:VeAkjQVzKLmu+JnFcK96TPbkuaTIqwGGAzQ9hgwPjVg=
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,43 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Info is metadata about a Cloud Foundry deployment
|
||||
type Info struct {
|
||||
Name string `json:"name"`
|
||||
Build string `json:"build"`
|
||||
Support string `json:"support"`
|
||||
Version int `json:"version"`
|
||||
Description string `json:"description"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
MinCLIVersion string `json:"min_cli_version"`
|
||||
MinRecommendedCLIVersion string `json:"min_recommended_cli_version"`
|
||||
APIVersion string `json:"api_version"`
|
||||
AppSSHEndpoint string `json:"app_ssh_endpoint"`
|
||||
AppSSHHostKeyFingerprint string `json:"app_ssh_host_key_fingerprint"`
|
||||
AppSSHOauthClient string `json:"app_ssh_oauth_client"`
|
||||
DopplerLoggingEndpoint string `json:"doppler_logging_endpoint"`
|
||||
RoutingEndpoint string `json:"routing_endpoint"`
|
||||
User string `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// GetInfo retrieves Info from the Cloud Controller API
|
||||
func (c *Client) GetInfo() (*Info, error) {
|
||||
r := c.NewRequest("GET", "/v2/info")
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting info")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var i Info
|
||||
err = json.NewDecoder(resp.Body).Decode(&i)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling info")
|
||||
}
|
||||
return &i, nil
|
||||
}
|
251
vendor/github.com/cloudfoundry-community/go-cfclient/isolationsegments.go
generated
vendored
Normal file
251
vendor/github.com/cloudfoundry-community/go-cfclient/isolationsegments.go
generated
vendored
Normal file
|
@ -0,0 +1,251 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type IsolationSegment struct {
|
||||
GUID string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type IsolationSegementResponse struct {
|
||||
GUID string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Links struct {
|
||||
Self struct {
|
||||
Href string `json:"href"`
|
||||
} `json:"self"`
|
||||
Spaces struct {
|
||||
Href string `json:"href"`
|
||||
} `json:"spaces"`
|
||||
Organizations struct {
|
||||
Href string `json:"href"`
|
||||
} `json:"organizations"`
|
||||
} `json:"links"`
|
||||
}
|
||||
|
||||
type ListIsolationSegmentsResponse struct {
|
||||
Pagination Pagination `json:"pagination"`
|
||||
Resources []IsolationSegementResponse `json:"resources"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateIsolationSegment(name string) (*IsolationSegment, error) {
|
||||
req := c.NewRequest("POST", "/v3/isolation_segments")
|
||||
req.obj = map[string]interface{}{
|
||||
"name": name,
|
||||
}
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error while creating isolation segment")
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("Error creating isolation segment %s, response code: %d", name, resp.StatusCode)
|
||||
}
|
||||
return respBodyToIsolationSegment(resp.Body, c)
|
||||
}
|
||||
|
||||
func respBodyToIsolationSegment(body io.ReadCloser, c *Client) (*IsolationSegment, error) {
|
||||
bodyRaw, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isr := IsolationSegementResponse{}
|
||||
err = json.Unmarshal(bodyRaw, &isr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IsolationSegment{
|
||||
GUID: isr.GUID,
|
||||
Name: isr.Name,
|
||||
CreatedAt: isr.CreatedAt,
|
||||
UpdatedAt: isr.UpdatedAt,
|
||||
c: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetIsolationSegmentByGUID(guid string) (*IsolationSegment, error) {
|
||||
var isr IsolationSegementResponse
|
||||
r := c.NewRequest("GET", "/v3/isolation_segments/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting isolation segment by GUID")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading isolation segment response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &isr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling isolation segment response")
|
||||
}
|
||||
return &IsolationSegment{Name: isr.Name, GUID: isr.GUID, CreatedAt: isr.CreatedAt, UpdatedAt: isr.UpdatedAt, c: c}, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListIsolationSegmentsByQuery(query url.Values) ([]IsolationSegment, error) {
|
||||
var iss []IsolationSegment
|
||||
requestUrl := "/v3/isolation_segments?" + query.Encode()
|
||||
for {
|
||||
var isr ListIsolationSegmentsResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting isolation segments")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading isolation segment request")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &isr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling isolation segment")
|
||||
}
|
||||
|
||||
for _, is := range isr.Resources {
|
||||
iss = append(iss, IsolationSegment{
|
||||
Name: is.Name,
|
||||
GUID: is.GUID,
|
||||
CreatedAt: is.CreatedAt,
|
||||
UpdatedAt: is.UpdatedAt,
|
||||
c: c,
|
||||
})
|
||||
}
|
||||
|
||||
var ok bool
|
||||
requestUrl, ok = isr.Pagination.Next.(string)
|
||||
if !ok || requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iss, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListIsolationSegments() ([]IsolationSegment, error) {
|
||||
return c.ListIsolationSegmentsByQuery(nil)
|
||||
}
|
||||
|
||||
// TODO listOrgsForIsolationSegments
|
||||
// TODO listSpacesForIsolationSegments
|
||||
// TODO setDefaultIsolationSegmentForOrg
|
||||
|
||||
func (c *Client) DeleteIsolationSegmentByGUID(guid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v3/isolation_segments/%s", guid)))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error during sending DELETE request for isolation segments")
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("Error deleting isolation segment %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IsolationSegment) Delete() error {
|
||||
return i.c.DeleteIsolationSegmentByGUID(i.GUID)
|
||||
}
|
||||
|
||||
func (c *Client) AddIsolationSegmentToOrg(isolationSegmentGUID, orgGUID string) error {
|
||||
isoSegment := IsolationSegment{GUID: isolationSegmentGUID, c: c}
|
||||
return isoSegment.AddOrg(orgGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveIsolationSegmentFromOrg(isolationSegmentGUID, orgGUID string) error {
|
||||
isoSegment := IsolationSegment{GUID: isolationSegmentGUID, c: c}
|
||||
return isoSegment.RemoveOrg(orgGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AddIsolationSegmentToSpace(isolationSegmentGUID, spaceGUID string) error {
|
||||
isoSegment := IsolationSegment{GUID: isolationSegmentGUID, c: c}
|
||||
return isoSegment.AddSpace(spaceGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveIsolationSegmentFromSpace(isolationSegmentGUID, spaceGUID string) error {
|
||||
isoSegment := IsolationSegment{GUID: isolationSegmentGUID, c: c}
|
||||
return isoSegment.RemoveSpace(spaceGUID)
|
||||
}
|
||||
|
||||
func (i *IsolationSegment) AddOrg(orgGuid string) error {
|
||||
if i == nil || i.c == nil {
|
||||
return errors.New("No communication handle.")
|
||||
}
|
||||
req := i.c.NewRequest("POST", fmt.Sprintf("/v3/isolation_segments/%s/relationships/organizations", i.GUID))
|
||||
type Entry struct {
|
||||
GUID string `json:"guid"`
|
||||
}
|
||||
req.obj = map[string]interface{}{
|
||||
"data": []Entry{{GUID: orgGuid}},
|
||||
}
|
||||
resp, err := i.c.DoRequest(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error during adding org to isolation segment")
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Error adding org %s to isolation segment %s, response code: %d", orgGuid, i.Name, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IsolationSegment) RemoveOrg(orgGuid string) error {
|
||||
if i == nil || i.c == nil {
|
||||
return errors.New("No communication handle.")
|
||||
}
|
||||
req := i.c.NewRequest("DELETE", fmt.Sprintf("/v3/isolation_segments/%s/relationships/organizations/%s", i.GUID, orgGuid))
|
||||
resp, err := i.c.DoRequest(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error during removing org %s in isolation segment %s", orgGuid, i.Name)
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("Error deleting org %s in isolation segment %s, response code: %d", orgGuid, i.Name, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IsolationSegment) AddSpace(spaceGuid string) error {
|
||||
if i == nil || i.c == nil {
|
||||
return errors.New("No communication handle.")
|
||||
}
|
||||
req := i.c.NewRequest("PUT", fmt.Sprintf("/v2/spaces/%s", spaceGuid))
|
||||
req.obj = map[string]interface{}{
|
||||
"isolation_segment_guid": i.GUID,
|
||||
}
|
||||
resp, err := i.c.DoRequest(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error during adding space %s to isolation segment %s", spaceGuid, i.Name)
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return fmt.Errorf("Error adding space to isolation segment %s, response code: %d", i.Name, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IsolationSegment) RemoveSpace(spaceGuid string) error {
|
||||
if i == nil || i.c == nil {
|
||||
return errors.New("No communication handle.")
|
||||
}
|
||||
req := i.c.NewRequest("DELETE", fmt.Sprintf("/v2/spaces/%s/isolation_segment", spaceGuid))
|
||||
resp, err := i.c.DoRequest(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error during deleting space %s in isolation segment %s", spaceGuid, i.Name)
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("Error deleting space %s from isolation segment %s, response code: %d", spaceGuid, i.Name, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
184
vendor/github.com/cloudfoundry-community/go-cfclient/org_quotas.go
generated
vendored
Normal file
184
vendor/github.com/cloudfoundry-community/go-cfclient/org_quotas.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type OrgQuotasResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []OrgQuotasResource `json:"resources"`
|
||||
}
|
||||
|
||||
type OrgQuotasResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity OrgQuota `json:"entity"`
|
||||
}
|
||||
|
||||
type OrgQuotaRequest struct {
|
||||
Name string `json:"name"`
|
||||
NonBasicServicesAllowed bool `json:"non_basic_services_allowed"`
|
||||
TotalServices int `json:"total_services"`
|
||||
TotalRoutes int `json:"total_routes"`
|
||||
TotalPrivateDomains int `json:"total_private_domains"`
|
||||
MemoryLimit int `json:"memory_limit"`
|
||||
TrialDBAllowed bool `json:"trial_db_allowed"`
|
||||
InstanceMemoryLimit int `json:"instance_memory_limit"`
|
||||
AppInstanceLimit int `json:"app_instance_limit"`
|
||||
AppTaskLimit int `json:"app_task_limit"`
|
||||
TotalServiceKeys int `json:"total_service_keys"`
|
||||
TotalReservedRoutePorts int `json:"total_reserved_route_ports"`
|
||||
}
|
||||
|
||||
type OrgQuota struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
NonBasicServicesAllowed bool `json:"non_basic_services_allowed"`
|
||||
TotalServices int `json:"total_services"`
|
||||
TotalRoutes int `json:"total_routes"`
|
||||
TotalPrivateDomains int `json:"total_private_domains"`
|
||||
MemoryLimit int `json:"memory_limit"`
|
||||
TrialDBAllowed bool `json:"trial_db_allowed"`
|
||||
InstanceMemoryLimit int `json:"instance_memory_limit"`
|
||||
AppInstanceLimit int `json:"app_instance_limit"`
|
||||
AppTaskLimit int `json:"app_task_limit"`
|
||||
TotalServiceKeys int `json:"total_service_keys"`
|
||||
TotalReservedRoutePorts int `json:"total_reserved_route_ports"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgQuotasByQuery(query url.Values) ([]OrgQuota, error) {
|
||||
var orgQuotas []OrgQuota
|
||||
requestUrl := "/v2/quota_definitions?" + query.Encode()
|
||||
for {
|
||||
orgQuotasResp, err := c.getOrgQuotasResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []OrgQuota{}, err
|
||||
}
|
||||
for _, org := range orgQuotasResp.Resources {
|
||||
org.Entity.Guid = org.Meta.Guid
|
||||
org.Entity.CreatedAt = org.Meta.CreatedAt
|
||||
org.Entity.UpdatedAt = org.Meta.UpdatedAt
|
||||
org.Entity.c = c
|
||||
orgQuotas = append(orgQuotas, org.Entity)
|
||||
}
|
||||
requestUrl = orgQuotasResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return orgQuotas, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgQuotas() ([]OrgQuota, error) {
|
||||
return c.ListOrgQuotasByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetOrgQuotaByName(name string) (OrgQuota, error) {
|
||||
q := url.Values{}
|
||||
q.Set("q", "name:"+name)
|
||||
orgQuotas, err := c.ListOrgQuotasByQuery(q)
|
||||
if err != nil {
|
||||
return OrgQuota{}, err
|
||||
}
|
||||
if len(orgQuotas) != 1 {
|
||||
return OrgQuota{}, fmt.Errorf("Unable to find org quota " + name)
|
||||
}
|
||||
return orgQuotas[0], nil
|
||||
}
|
||||
|
||||
func (c *Client) getOrgQuotasResponse(requestUrl string) (OrgQuotasResponse, error) {
|
||||
var orgQuotasResp OrgQuotasResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return OrgQuotasResponse{}, errors.Wrap(err, "Error requesting org quotas")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return OrgQuotasResponse{}, errors.Wrap(err, "Error reading org quotas body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &orgQuotasResp)
|
||||
if err != nil {
|
||||
return OrgQuotasResponse{}, errors.Wrap(err, "Error unmarshalling org quotas")
|
||||
}
|
||||
return orgQuotasResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateOrgQuota(orgQuote OrgQuotaRequest) (*OrgQuota, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(orgQuote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/quota_definitions", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleOrgQuotaResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateOrgQuota(orgQuotaGUID string, orgQuota OrgQuotaRequest) (*OrgQuota, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(orgQuota)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/quota_definitions/%s", orgQuotaGUID), buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleOrgQuotaResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteOrgQuota(guid string, async bool) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/quota_definitions/%s?async=%t", guid, async)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (async && (resp.StatusCode != http.StatusAccepted)) || (!async && (resp.StatusCode != http.StatusNoContent)) {
|
||||
return errors.Wrapf(err, "Error deleting organization %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleOrgQuotaResp(resp *http.Response) (*OrgQuota, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var orgQuotasResource OrgQuotasResource
|
||||
err = json.Unmarshal(body, &orgQuotasResource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.mergeOrgQuotaResource(orgQuotasResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeOrgQuotaResource(orgQuotaResource OrgQuotasResource) *OrgQuota {
|
||||
orgQuotaResource.Entity.Guid = orgQuotaResource.Meta.Guid
|
||||
orgQuotaResource.Entity.CreatedAt = orgQuotaResource.Meta.CreatedAt
|
||||
orgQuotaResource.Entity.UpdatedAt = orgQuotaResource.Meta.UpdatedAt
|
||||
orgQuotaResource.Entity.c = c
|
||||
return &orgQuotaResource.Entity
|
||||
}
|
|
@ -0,0 +1,832 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type OrgResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []OrgResource `json:"resources"`
|
||||
}
|
||||
|
||||
type OrgResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Org `json:"entity"`
|
||||
}
|
||||
|
||||
type OrgUserResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextURL string `json:"next_url"`
|
||||
Resources []UserResource `json:"resources"`
|
||||
}
|
||||
|
||||
type Org struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
QuotaDefinitionGuid string `json:"quota_definition_guid"`
|
||||
DefaultIsolationSegmentGuid string `json:"default_isolation_segment_guid"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type OrgSummary struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Spaces []OrgSummarySpaces `json:"spaces"`
|
||||
}
|
||||
|
||||
type OrgSummarySpaces struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
ServiceCount int `json:"service_count"`
|
||||
AppCount int `json:"app_count"`
|
||||
MemDevTotal int `json:"mem_dev_total"`
|
||||
MemProdTotal int `json:"mem_prod_total"`
|
||||
}
|
||||
|
||||
type OrgRequest struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status,omitempty"`
|
||||
QuotaDefinitionGuid string `json:"quota_definition_guid,omitempty"`
|
||||
DefaultIsolationSegmentGuid string `json:"default_isolation_segment_guid,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgsByQuery(query url.Values) ([]Org, error) {
|
||||
var orgs []Org
|
||||
requestURL := "/v2/organizations?" + query.Encode()
|
||||
for {
|
||||
orgResp, err := c.getOrgResponse(requestURL)
|
||||
if err != nil {
|
||||
return []Org{}, err
|
||||
}
|
||||
for _, org := range orgResp.Resources {
|
||||
orgs = append(orgs, c.mergeOrgResource(org))
|
||||
}
|
||||
requestURL = orgResp.NextUrl
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return orgs, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgs() ([]Org, error) {
|
||||
return c.ListOrgsByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetOrgByName(name string) (Org, error) {
|
||||
var org Org
|
||||
q := url.Values{}
|
||||
q.Set("q", "name:"+name)
|
||||
orgs, err := c.ListOrgsByQuery(q)
|
||||
if err != nil {
|
||||
return org, err
|
||||
}
|
||||
if len(orgs) == 0 {
|
||||
return org, fmt.Errorf("Unable to find org %s", name)
|
||||
}
|
||||
return orgs[0], nil
|
||||
}
|
||||
|
||||
func (c *Client) GetOrgByGuid(guid string) (Org, error) {
|
||||
var orgRes OrgResource
|
||||
r := c.NewRequest("GET", "/v2/organizations/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &orgRes)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
return c.mergeOrgResource(orgRes), nil
|
||||
}
|
||||
|
||||
func (c *Client) OrgSpaces(guid string) ([]Space, error) {
|
||||
return c.fetchSpaces(fmt.Sprintf("/v2/organizations/%s/spaces", guid))
|
||||
}
|
||||
|
||||
func (o *Org) Summary() (OrgSummary, error) {
|
||||
var orgSummary OrgSummary
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/summary", o.Guid)
|
||||
r := o.c.NewRequest("GET", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return OrgSummary{}, errors.Wrap(err, "Error requesting org summary")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return OrgSummary{}, errors.Wrap(err, "Error reading org summary body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &orgSummary)
|
||||
if err != nil {
|
||||
return OrgSummary{}, errors.Wrap(err, "Error unmarshalling org summary")
|
||||
}
|
||||
return orgSummary, nil
|
||||
}
|
||||
|
||||
func (o *Org) Quota() (*OrgQuota, error) {
|
||||
var orgQuota *OrgQuota
|
||||
var orgQuotaResource OrgQuotasResource
|
||||
if o.QuotaDefinitionGuid == "" {
|
||||
return nil, nil
|
||||
}
|
||||
requestURL := fmt.Sprintf("/v2/quota_definitions/%s", o.QuotaDefinitionGuid)
|
||||
r := o.c.NewRequest("GET", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return &OrgQuota{}, errors.Wrap(err, "Error requesting org quota")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return &OrgQuota{}, errors.Wrap(err, "Error reading org quota body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &orgQuotaResource)
|
||||
if err != nil {
|
||||
return &OrgQuota{}, errors.Wrap(err, "Error unmarshalling org quota")
|
||||
}
|
||||
orgQuota = &orgQuotaResource.Entity
|
||||
orgQuota.Guid = orgQuotaResource.Meta.Guid
|
||||
orgQuota.c = o.c
|
||||
return orgQuota, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgUsersByQuery(orgGUID string, query url.Values) ([]User, error) {
|
||||
var users []User
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/users?%s", orgGUID, query.Encode())
|
||||
for {
|
||||
omResp, err := c.getOrgUserResponse(requestURL)
|
||||
if err != nil {
|
||||
return []User{}, err
|
||||
}
|
||||
for _, u := range omResp.Resources {
|
||||
users = append(users, c.mergeUserResource(u))
|
||||
}
|
||||
requestURL = omResp.NextURL
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgUsers(orgGUID string) ([]User, error) {
|
||||
return c.ListOrgUsersByQuery(orgGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) listOrgRolesByQuery(orgGUID, role string, query url.Values) ([]User, error) {
|
||||
var users []User
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/%s?%s", orgGUID, role, query.Encode())
|
||||
for {
|
||||
omResp, err := c.getOrgUserResponse(requestURL)
|
||||
if err != nil {
|
||||
return []User{}, err
|
||||
}
|
||||
for _, u := range omResp.Resources {
|
||||
users = append(users, c.mergeUserResource(u))
|
||||
}
|
||||
requestURL = omResp.NextURL
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgManagersByQuery(orgGUID string, query url.Values) ([]User, error) {
|
||||
return c.listOrgRolesByQuery(orgGUID, "managers", query)
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgManagers(orgGUID string) ([]User, error) {
|
||||
return c.ListOrgManagersByQuery(orgGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgAuditorsByQuery(orgGUID string, query url.Values) ([]User, error) {
|
||||
return c.listOrgRolesByQuery(orgGUID, "auditors", query)
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgAuditors(orgGUID string) ([]User, error) {
|
||||
return c.ListOrgAuditorsByQuery(orgGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgBillingManagersByQuery(orgGUID string, query url.Values) ([]User, error) {
|
||||
return c.listOrgRolesByQuery(orgGUID, "billing_managers", query)
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgBillingManagers(orgGUID string) ([]User, error) {
|
||||
return c.ListOrgBillingManagersByQuery(orgGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgManager(orgGUID, userGUID string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateManager(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgManagerByUsername(orgGUID, name string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateManagerByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgManagerByUsernameAndOrigin(orgGUID, name, origin string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateManagerByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgUser(orgGUID, userGUID string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateUser(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgAuditor(orgGUID, userGUID string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateAuditor(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgUserByUsername(orgGUID, name string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateUserByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgUserByUsernameAndOrigin(orgGUID, name, origin string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateUserByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgAuditorByUsername(orgGUID, name string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateAuditorByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgAuditorByUsernameAndOrigin(orgGUID, name, origin string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateAuditorByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgBillingManager(orgGUID, userGUID string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateBillingManager(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgBillingManagerByUsername(orgGUID, name string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateBillingManagerByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateOrgBillingManagerByUsernameAndOrigin(orgGUID, name, origin string) (Org, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.AssociateBillingManagerByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgManager(orgGUID, userGUID string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveManager(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgManagerByUsername(orgGUID, name string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveManagerByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgManagerByUsernameAndOrigin(orgGUID, name, origin string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveManagerByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgUser(orgGUID, userGUID string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveUser(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgAuditor(orgGUID, userGUID string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveAuditor(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgUserByUsername(orgGUID, name string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveUserByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgUserByUsernameAndOrigin(orgGUID, name, origin string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveUserByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgAuditorByUsername(orgGUID, name string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveAuditorByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgAuditorByUsernameAndOrigin(orgGUID, name, origin string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveAuditorByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgBillingManager(orgGUID, userGUID string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveBillingManager(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgBillingManagerByUsername(orgGUID, name string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveBillingManagerByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveOrgBillingManagerByUsernameAndOrigin(orgGUID, name, origin string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.RemoveBillingManagerByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgSpaceQuotas(orgGUID string) ([]SpaceQuota, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.ListSpaceQuotas()
|
||||
}
|
||||
|
||||
func (c *Client) ListOrgPrivateDomains(orgGUID string) ([]Domain, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.ListPrivateDomains()
|
||||
}
|
||||
|
||||
func (c *Client) ShareOrgPrivateDomain(orgGUID, privateDomainGUID string) (*Domain, error) {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.SharePrivateDomain(privateDomainGUID)
|
||||
}
|
||||
|
||||
func (c *Client) UnshareOrgPrivateDomain(orgGUID, privateDomainGUID string) error {
|
||||
org := Org{Guid: orgGUID, c: c}
|
||||
return org.UnsharePrivateDomain(privateDomainGUID)
|
||||
}
|
||||
|
||||
func (o *Org) ListSpaceQuotas() ([]SpaceQuota, error) {
|
||||
var spaceQuotas []SpaceQuota
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/space_quota_definitions", o.Guid)
|
||||
for {
|
||||
spaceQuotasResp, err := o.c.getSpaceQuotasResponse(requestURL)
|
||||
if err != nil {
|
||||
return []SpaceQuota{}, err
|
||||
}
|
||||
for _, resource := range spaceQuotasResp.Resources {
|
||||
spaceQuotas = append(spaceQuotas, *o.c.mergeSpaceQuotaResource(resource))
|
||||
}
|
||||
requestURL = spaceQuotasResp.NextUrl
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return spaceQuotas, nil
|
||||
}
|
||||
|
||||
func (o *Org) ListPrivateDomains() ([]Domain, error) {
|
||||
var domains []Domain
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/private_domains", o.Guid)
|
||||
for {
|
||||
domainsResp, err := o.c.getDomainsResponse(requestURL)
|
||||
if err != nil {
|
||||
return []Domain{}, err
|
||||
}
|
||||
for _, resource := range domainsResp.Resources {
|
||||
domains = append(domains, *o.c.mergeDomainResource(resource))
|
||||
}
|
||||
requestURL = domainsResp.NextUrl
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (o *Org) SharePrivateDomain(privateDomainGUID string) (*Domain, error) {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/private_domains/%s", o.Guid, privateDomainGUID)
|
||||
r := o.c.NewRequest("PUT", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, errors.Wrapf(err, "Error sharing domain %s for org %s, response code: %d", privateDomainGUID, o.Guid, resp.StatusCode)
|
||||
}
|
||||
return o.c.handleDomainResp(resp)
|
||||
}
|
||||
|
||||
func (o *Org) UnsharePrivateDomain(privateDomainGUID string) error {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/private_domains/%s", o.Guid, privateDomainGUID)
|
||||
r := o.c.NewRequest("DELETE", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error unsharing domain %s for org %s, response code: %d", privateDomainGUID, o.Guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Org) associateRole(userGUID, role string) (Org, error) {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/%s/%s", o.Guid, role, userGUID)
|
||||
r := o.c.NewRequest("PUT", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Org{}, errors.Wrapf(err, "Error associating %s %s, response code: %d", role, userGUID, resp.StatusCode)
|
||||
}
|
||||
return o.c.handleOrgResp(resp)
|
||||
}
|
||||
|
||||
func (o *Org) associateRoleByUsernameAndOrigin(name, role, origin string) (Org, error) {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/%s", o.Guid, role)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
payload := make(map[string]string)
|
||||
payload["username"] = name
|
||||
if origin != "" {
|
||||
payload["origin"] = origin
|
||||
}
|
||||
err := json.NewEncoder(buf).Encode(payload)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
r := o.c.NewRequestWithBody("PUT", requestURL, buf)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Org{}, errors.Wrapf(err, "Error associating %s %s, response code: %d", role, name, resp.StatusCode)
|
||||
}
|
||||
return o.c.handleOrgResp(resp)
|
||||
}
|
||||
|
||||
func (o *Org) AssociateManager(userGUID string) (Org, error) {
|
||||
return o.associateRole(userGUID, "managers")
|
||||
}
|
||||
|
||||
func (o *Org) AssociateManagerByUsername(name string) (Org, error) {
|
||||
return o.associateRoleByUsernameAndOrigin(name, "managers", "")
|
||||
}
|
||||
|
||||
func (o *Org) AssociateManagerByUsernameAndOrigin(name, origin string) (Org, error) {
|
||||
return o.associateRoleByUsernameAndOrigin(name, "managers", origin)
|
||||
}
|
||||
|
||||
func (o *Org) AssociateUser(userGUID string) (Org, error) {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/users/%s", o.Guid, userGUID)
|
||||
r := o.c.NewRequest("PUT", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Org{}, errors.Wrapf(err, "Error associating user %s, response code: %d", userGUID, resp.StatusCode)
|
||||
}
|
||||
return o.c.handleOrgResp(resp)
|
||||
}
|
||||
|
||||
func (o *Org) AssociateAuditor(userGUID string) (Org, error) {
|
||||
return o.associateRole(userGUID, "auditors")
|
||||
}
|
||||
|
||||
func (o *Org) AssociateAuditorByUsername(name string) (Org, error) {
|
||||
return o.associateRoleByUsernameAndOrigin(name, "auditors", "")
|
||||
}
|
||||
|
||||
func (o *Org) AssociateAuditorByUsernameAndOrigin(name, origin string) (Org, error) {
|
||||
return o.associateRoleByUsernameAndOrigin(name, "auditors", origin)
|
||||
}
|
||||
|
||||
func (o *Org) AssociateBillingManager(userGUID string) (Org, error) {
|
||||
return o.associateRole(userGUID, "billing_managers")
|
||||
}
|
||||
|
||||
func (o *Org) AssociateBillingManagerByUsername(name string) (Org, error) {
|
||||
return o.associateRoleByUsernameAndOrigin(name, "billing_managers", "")
|
||||
}
|
||||
func (o *Org) AssociateBillingManagerByUsernameAndOrigin(name, origin string) (Org, error) {
|
||||
return o.associateRoleByUsernameAndOrigin(name, "billing_managers", origin)
|
||||
}
|
||||
|
||||
func (o *Org) AssociateUserByUsername(name string) (Org, error) {
|
||||
return o.associateUserByUsernameAndOrigin(name, "")
|
||||
}
|
||||
|
||||
func (o *Org) AssociateUserByUsernameAndOrigin(name, origin string) (Org, error) {
|
||||
return o.associateUserByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (o *Org) associateUserByUsernameAndOrigin(name, origin string) (Org, error) {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/users", o.Guid)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
payload := make(map[string]string)
|
||||
payload["username"] = name
|
||||
if origin != "" {
|
||||
payload["origin"] = origin
|
||||
}
|
||||
err := json.NewEncoder(buf).Encode(payload)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
r := o.c.NewRequestWithBody("PUT", requestURL, buf)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Org{}, errors.Wrapf(err, "Error associating user %s, response code: %d", name, resp.StatusCode)
|
||||
}
|
||||
return o.c.handleOrgResp(resp)
|
||||
}
|
||||
|
||||
func (o *Org) removeRole(userGUID, role string) error {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/%s/%s", o.Guid, role, userGUID)
|
||||
r := o.c.NewRequest("DELETE", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error removing %s %s, response code: %d", role, userGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Org) removeRoleByUsernameAndOrigin(name, role, origin string) error {
|
||||
var requestURL string
|
||||
var method string
|
||||
buf := bytes.NewBuffer(nil)
|
||||
payload := make(map[string]string)
|
||||
payload["username"] = name
|
||||
if origin != "" {
|
||||
requestURL = fmt.Sprintf("/v2/organizations/%s/%s/remove", o.Guid, role)
|
||||
method = "POST"
|
||||
payload["origin"] = origin
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("/v2/organizations/%s/%s", o.Guid, role)
|
||||
method = "DELETE"
|
||||
}
|
||||
err := json.NewEncoder(buf).Encode(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := o.c.NewRequestWithBody(method, requestURL, buf)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error removing manager %s, response code: %d", name, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Org) RemoveManager(userGUID string) error {
|
||||
return o.removeRole(userGUID, "managers")
|
||||
}
|
||||
|
||||
func (o *Org) RemoveManagerByUsername(name string) error {
|
||||
return o.removeRoleByUsernameAndOrigin(name, "managers", "")
|
||||
}
|
||||
func (o *Org) RemoveManagerByUsernameAndOrigin(name, origin string) error {
|
||||
return o.removeRoleByUsernameAndOrigin(name, "managers", origin)
|
||||
}
|
||||
|
||||
func (o *Org) RemoveAuditor(userGUID string) error {
|
||||
return o.removeRole(userGUID, "auditors")
|
||||
}
|
||||
|
||||
func (o *Org) RemoveAuditorByUsername(name string) error {
|
||||
return o.removeRoleByUsernameAndOrigin(name, "auditors", "")
|
||||
}
|
||||
func (o *Org) RemoveAuditorByUsernameAndOrigin(name, origin string) error {
|
||||
return o.removeRoleByUsernameAndOrigin(name, "auditors", origin)
|
||||
}
|
||||
|
||||
func (o *Org) RemoveBillingManager(userGUID string) error {
|
||||
return o.removeRole(userGUID, "billing_managers")
|
||||
}
|
||||
|
||||
func (o *Org) RemoveBillingManagerByUsername(name string) error {
|
||||
return o.removeRoleByUsernameAndOrigin(name, "billing_managers", "")
|
||||
}
|
||||
|
||||
func (o *Org) RemoveBillingManagerByUsernameAndOrigin(name, origin string) error {
|
||||
return o.removeRoleByUsernameAndOrigin(name, "billing_managers", origin)
|
||||
}
|
||||
|
||||
func (o *Org) RemoveUser(userGUID string) error {
|
||||
requestURL := fmt.Sprintf("/v2/organizations/%s/users/%s", o.Guid, userGUID)
|
||||
r := o.c.NewRequest("DELETE", requestURL)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error removing user %s, response code: %d", userGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Org) RemoveUserByUsername(name string) error {
|
||||
return o.removeUserByUsernameAndOrigin(name, "")
|
||||
}
|
||||
|
||||
func (o *Org) RemoveUserByUsernameAndOrigin(name, origin string) error {
|
||||
return o.removeUserByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (o *Org) removeUserByUsernameAndOrigin(name, origin string) error {
|
||||
var requestURL string
|
||||
var method string
|
||||
buf := bytes.NewBuffer(nil)
|
||||
payload := make(map[string]string)
|
||||
payload["username"] = name
|
||||
if origin != "" {
|
||||
payload["origin"] = origin
|
||||
requestURL = fmt.Sprintf("/v2/organizations/%s/users/remove", o.Guid)
|
||||
method = "POST"
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("/v2/organizations/%s/users", o.Guid)
|
||||
method = "DELETE"
|
||||
}
|
||||
err := json.NewEncoder(buf).Encode(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := o.c.NewRequestWithBody(method, requestURL, buf)
|
||||
resp, err := o.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error removing user %s, response code: %d", name, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateOrg(req OrgRequest) (Org, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/organizations", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Org{}, errors.Wrapf(err, "Error creating organization, response code: %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleOrgResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateOrg(orgGUID string, orgRequest OrgRequest) (Org, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(orgRequest)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
r := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/organizations/%s", orgGUID), buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Org{}, errors.Wrapf(err, "Error updating organization, response code: %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleOrgResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteOrg(guid string, recursive, async bool) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/organizations/%s?recursive=%t&async=%t", guid, recursive, async)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting organization %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getOrgResponse(requestURL string) (OrgResponse, error) {
|
||||
var orgResp OrgResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return OrgResponse{}, errors.Wrap(err, "Error requesting orgs")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return OrgResponse{}, errors.Wrap(err, "Error reading org request")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &orgResp)
|
||||
if err != nil {
|
||||
return OrgResponse{}, errors.Wrap(err, "Error unmarshalling org")
|
||||
}
|
||||
return orgResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) fetchOrgs(requestURL string) ([]Org, error) {
|
||||
var orgs []Org
|
||||
for {
|
||||
orgResp, err := c.getOrgResponse(requestURL)
|
||||
if err != nil {
|
||||
return []Org{}, err
|
||||
}
|
||||
for _, org := range orgResp.Resources {
|
||||
orgs = append(orgs, c.mergeOrgResource(org))
|
||||
}
|
||||
requestURL = orgResp.NextUrl
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return orgs, nil
|
||||
}
|
||||
|
||||
func (c *Client) handleOrgResp(resp *http.Response) (Org, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
var orgResource OrgResource
|
||||
err = json.Unmarshal(body, &orgResource)
|
||||
if err != nil {
|
||||
return Org{}, err
|
||||
}
|
||||
return c.mergeOrgResource(orgResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) getOrgUserResponse(requestURL string) (OrgUserResponse, error) {
|
||||
var omResp OrgUserResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return OrgUserResponse{}, errors.Wrap(err, "error requesting org managers")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return OrgUserResponse{}, errors.Wrap(err, "error reading org managers response body")
|
||||
}
|
||||
if err := json.Unmarshal(resBody, &omResp); err != nil {
|
||||
return OrgUserResponse{}, errors.Wrap(err, "error unmarshaling org managers")
|
||||
}
|
||||
return omResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeOrgResource(org OrgResource) Org {
|
||||
org.Entity.Guid = org.Meta.Guid
|
||||
org.Entity.CreatedAt = org.Meta.CreatedAt
|
||||
org.Entity.UpdatedAt = org.Meta.UpdatedAt
|
||||
org.Entity.c = c
|
||||
return org.Entity
|
||||
}
|
||||
|
||||
func (c *Client) DefaultIsolationSegmentForOrg(orgGUID, isolationSegmentGUID string) error {
|
||||
return c.updateOrgDefaultIsolationSegment(orgGUID, map[string]interface{}{"guid": isolationSegmentGUID})
|
||||
}
|
||||
|
||||
func (c *Client) ResetDefaultIsolationSegmentForOrg(orgGUID string) error {
|
||||
return c.updateOrgDefaultIsolationSegment(orgGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) updateOrgDefaultIsolationSegment(orgGUID string, data interface{}) error {
|
||||
requestURL := fmt.Sprintf("/v3/organizations/%s/relationships/default_isolation_segment", orgGUID)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(map[string]interface{}{"data": data})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := c.NewRequestWithBody("PATCH", requestURL, buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.Wrapf(err, "Error setting default isolation segment for org %s, response code: %d", orgGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
124
vendor/github.com/cloudfoundry-community/go-cfclient/processes.go
generated
vendored
Normal file
124
vendor/github.com/cloudfoundry-community/go-cfclient/processes.go
generated
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ProcessListResponse is the json body returned from the API
|
||||
type ProcessListResponse struct {
|
||||
Pagination Pagination `json:"pagination"`
|
||||
Processes []Process `json:"resources"`
|
||||
}
|
||||
|
||||
// Process represents a running process in a container.
|
||||
type Process struct {
|
||||
GUID string `json:"guid"`
|
||||
Type string `json:"type"`
|
||||
Instances int `json:"instances"`
|
||||
MemoryInMB int `json:"memory_in_mb"`
|
||||
DiskInMB int `json:"disk_in_mb"`
|
||||
Ports []int `json:"ports,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
HealthCheck struct {
|
||||
Type string `json:"type"`
|
||||
Data struct {
|
||||
Timeout int `json:"timeout"`
|
||||
InvocationTimeout int `json:"invocation_timeout"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
} `json:"data"`
|
||||
} `json:"health_check"`
|
||||
Links struct {
|
||||
Self Link `json:"self"`
|
||||
Scale Link `json:"scale"`
|
||||
App Link `json:"app"`
|
||||
Space Link `json:"space"`
|
||||
Stats Link `json:"stats"`
|
||||
} `json:"links"`
|
||||
}
|
||||
|
||||
// ListAllProcesses will call the v3 processes api
|
||||
func (c *Client) ListAllProcesses() ([]Process, error) {
|
||||
return c.ListAllProcessesByQuery(url.Values{})
|
||||
}
|
||||
|
||||
// ListAllProcessesByQuery will call the v3 processes api
|
||||
func (c *Client) ListAllProcessesByQuery(query url.Values) ([]Process, error) {
|
||||
var allProcesses []Process
|
||||
|
||||
urlPath := "/v3/processes"
|
||||
for {
|
||||
resp, err := c.getProcessPage(urlPath, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Pagination.TotalResults == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if allProcesses == nil {
|
||||
allProcesses = make([]Process, 0, resp.Pagination.TotalResults)
|
||||
}
|
||||
|
||||
allProcesses = append(allProcesses, resp.Processes...)
|
||||
if resp.Pagination.Next == nil {
|
||||
return allProcesses, nil
|
||||
}
|
||||
|
||||
var nextURL string
|
||||
|
||||
if resp.Pagination.Next == nil {
|
||||
return allProcesses, nil
|
||||
}
|
||||
|
||||
switch resp.Pagination.Next.(type) {
|
||||
case string:
|
||||
nextURL = resp.Pagination.Next.(string)
|
||||
case map[string]interface{}:
|
||||
m := resp.Pagination.Next.(map[string]interface{})
|
||||
u, ok := m["href"]
|
||||
if ok {
|
||||
nextURL = u.(string)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected type [%s] for next url", reflect.TypeOf(resp.Pagination.Next).String())
|
||||
}
|
||||
|
||||
if nextURL == "" {
|
||||
return allProcesses, nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(nextURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urlPath = u.Path
|
||||
query, err = url.ParseQuery(u.RawQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) getProcessPage(urlPath string, query url.Values) (*ProcessListResponse, error) {
|
||||
req := c.NewRequest("GET", fmt.Sprintf("%s?%s", urlPath, query.Encode()))
|
||||
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
procResp := new(ProcessListResponse)
|
||||
defer resp.Body.Close()
|
||||
err = json.NewDecoder(resp.Body).Decode(procResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return procResp, nil
|
||||
}
|
159
vendor/github.com/cloudfoundry-community/go-cfclient/route_mappings.go
generated
vendored
Normal file
159
vendor/github.com/cloudfoundry-community/go-cfclient/route_mappings.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type RouteMappingRequest struct {
|
||||
AppGUID string `json:"app_guid"`
|
||||
RouteGUID string `json:"route_guid"`
|
||||
AppPort int `json:"app_port"`
|
||||
}
|
||||
|
||||
type RouteMappingResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []RouteMappingResource `json:"resources"`
|
||||
}
|
||||
|
||||
type RouteMapping struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
AppPort int `json:"app_port"`
|
||||
AppGUID string `json:"app_guid"`
|
||||
RouteGUID string `json:"route_guid"`
|
||||
AppUrl string `json:"app_url"`
|
||||
RouteUrl string `json:"route_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type RouteMappingResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity RouteMapping `json:"entity"`
|
||||
}
|
||||
|
||||
func (c *Client) MappingAppAndRoute(req RouteMappingRequest) (*RouteMapping, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/route_mappings", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleMappingResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) ListRouteMappings() ([]*RouteMapping, error) {
|
||||
return c.ListRouteMappingsByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) ListRouteMappingsByQuery(query url.Values) ([]*RouteMapping, error) {
|
||||
var routeMappings []*RouteMapping
|
||||
var routeMappingsResp RouteMappingResponse
|
||||
pages := 0
|
||||
|
||||
requestUrl := "/v2/route_mappings?" + query.Encode()
|
||||
for {
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting route mappings")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading route mappings request:")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &routeMappingsResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling route mappings")
|
||||
}
|
||||
|
||||
for _, routeMapping := range routeMappingsResp.Resources {
|
||||
routeMappings = append(routeMappings, c.mergeRouteMappingResource(routeMapping))
|
||||
}
|
||||
requestUrl = routeMappingsResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
pages++
|
||||
totalPages := routeMappingsResp.Pages
|
||||
if totalPages > 0 && pages >= totalPages {
|
||||
break
|
||||
}
|
||||
}
|
||||
return routeMappings, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetRouteMappingByGuid(guid string) (*RouteMapping, error) {
|
||||
var routeMapping RouteMappingResource
|
||||
requestUrl := fmt.Sprintf("/v2/route_mappings/%s", guid)
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting route mapping")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading route mapping response body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &routeMapping)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshalling route mapping")
|
||||
}
|
||||
routeMapping.Entity.Guid = routeMapping.Meta.Guid
|
||||
routeMapping.Entity.CreatedAt = routeMapping.Meta.CreatedAt
|
||||
routeMapping.Entity.UpdatedAt = routeMapping.Meta.UpdatedAt
|
||||
routeMapping.Entity.c = c
|
||||
return &routeMapping.Entity, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteRouteMapping(guid string) error {
|
||||
requestUrl := fmt.Sprintf("/v2/route_mappings/%s?", guid)
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", requestUrl))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting route mapping %s, response code %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleMappingResp(resp *http.Response) (*RouteMapping, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var mappingResource RouteMappingResource
|
||||
err = json.Unmarshal(body, &mappingResource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.mergeRouteMappingResource(mappingResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeRouteMappingResource(mapping RouteMappingResource) *RouteMapping {
|
||||
mapping.Entity.Guid = mapping.Meta.Guid
|
||||
mapping.Entity.CreatedAt = mapping.Meta.CreatedAt
|
||||
mapping.Entity.UpdatedAt = mapping.Meta.UpdatedAt
|
||||
mapping.Entity.c = c
|
||||
return &mapping.Entity
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type RoutesResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []RoutesResource `json:"resources"`
|
||||
}
|
||||
|
||||
type RoutesResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Route `json:"entity"`
|
||||
}
|
||||
|
||||
type RouteRequest struct {
|
||||
DomainGuid string `json:"domain_guid"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
Host string `json:"host"` // required for http routes
|
||||
Path string `json:"path"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
DomainGuid string `json:"domain_guid"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
ServiceInstanceGuid string `json:"service_instance_guid"`
|
||||
Port int `json:"port"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
// CreateRoute creates a regular http route
|
||||
func (c *Client) CreateRoute(routeRequest RouteRequest) (Route, error) {
|
||||
routesResource, err := c.createRoute("/v2/routes", routeRequest)
|
||||
if nil != err {
|
||||
return Route{}, err
|
||||
}
|
||||
return c.mergeRouteResource(routesResource), nil
|
||||
}
|
||||
|
||||
// CreateTcpRoute creates a TCP route
|
||||
func (c *Client) CreateTcpRoute(routeRequest RouteRequest) (Route, error) {
|
||||
routesResource, err := c.createRoute("/v2/routes?generate_port=true", routeRequest)
|
||||
if nil != err {
|
||||
return Route{}, err
|
||||
}
|
||||
return c.mergeRouteResource(routesResource), nil
|
||||
}
|
||||
|
||||
// BindRoute associates the specified route with the application
|
||||
func (c *Client) BindRoute(routeGUID, appGUID string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/routes/%s/apps/%s", routeGUID, appGUID)))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error binding route %s to app %s", routeGUID, appGUID)
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return fmt.Errorf("Error binding route %s to app %s, response code: %d", routeGUID, appGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ListRoutesByQuery(query url.Values) ([]Route, error) {
|
||||
return c.fetchRoutes("/v2/routes?" + query.Encode())
|
||||
}
|
||||
|
||||
func (c *Client) fetchRoutes(requestUrl string) ([]Route, error) {
|
||||
var routes []Route
|
||||
for {
|
||||
routesResp, err := c.getRoutesResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []Route{}, err
|
||||
}
|
||||
for _, route := range routesResp.Resources {
|
||||
route.Entity.Guid = route.Meta.Guid
|
||||
route.Entity.CreatedAt = route.Meta.CreatedAt
|
||||
route.Entity.UpdatedAt = route.Meta.UpdatedAt
|
||||
route.Entity.c = c
|
||||
routes = append(routes, route.Entity)
|
||||
}
|
||||
requestUrl = routesResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListRoutes() ([]Route, error) {
|
||||
return c.ListRoutesByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) getRoutesResponse(requestUrl string) (RoutesResponse, error) {
|
||||
var routesResp RoutesResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return RoutesResponse{}, errors.Wrap(err, "Error requesting routes")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return RoutesResponse{}, errors.Wrap(err, "Error reading routes body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &routesResp)
|
||||
if err != nil {
|
||||
return RoutesResponse{}, errors.Wrap(err, "Error unmarshalling routes")
|
||||
}
|
||||
return routesResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) createRoute(requestUrl string, routeRequest RouteRequest) (RoutesResource, error) {
|
||||
var routeResp RoutesResource
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(routeRequest)
|
||||
if err != nil {
|
||||
return RoutesResource{}, errors.Wrap(err, "Error creating route - failed to serialize request body")
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", requestUrl, buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return RoutesResource{}, errors.Wrap(err, "Error creating route")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return RoutesResource{}, errors.Wrap(err, "Error creating route")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &routeResp)
|
||||
if err != nil {
|
||||
return RoutesResource{}, errors.Wrap(err, "Error unmarshalling routes")
|
||||
}
|
||||
routeResp.Entity.c = c
|
||||
return routeResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteRoute(guid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/routes/%s", guid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting route %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeRouteResource(rr RoutesResource) Route {
|
||||
rr.Entity.Guid = rr.Meta.Guid
|
||||
rr.Entity.CreatedAt = rr.Meta.CreatedAt
|
||||
rr.Entity.UpdatedAt = rr.Meta.UpdatedAt
|
||||
rr.Entity.c = c
|
||||
return rr.Entity
|
||||
}
|
565
vendor/github.com/cloudfoundry-community/go-cfclient/secgroups.go
generated
vendored
Normal file
565
vendor/github.com/cloudfoundry-community/go-cfclient/secgroups.go
generated
vendored
Normal file
|
@ -0,0 +1,565 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type SecGroupResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []SecGroupResource `json:"resources"`
|
||||
}
|
||||
|
||||
type SecGroupCreateResponse struct {
|
||||
Code int `json:"code"`
|
||||
ErrorCode string `json:"error_code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type SecGroupResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity SecGroup `json:"entity"`
|
||||
}
|
||||
|
||||
type SecGroup struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Rules []SecGroupRule `json:"rules"`
|
||||
Running bool `json:"running_default"`
|
||||
Staging bool `json:"staging_default"`
|
||||
SpacesURL string `json:"spaces_url"`
|
||||
StagingSpacesURL string `json:"staging_spaces_url"`
|
||||
SpacesData []SpaceResource `json:"spaces"`
|
||||
StagingSpacesData []SpaceResource `json:"staging_spaces"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type SecGroupRule struct {
|
||||
Protocol string `json:"protocol"`
|
||||
Ports string `json:"ports,omitempty"` //e.g. "4000-5000,9142"
|
||||
Destination string `json:"destination"` //CIDR Format
|
||||
Description string `json:"description,omitempty"` //Optional description
|
||||
Code int `json:"code"` // ICMP code
|
||||
Type int `json:"type"` //ICMP type. Only valid if Protocol=="icmp"
|
||||
Log bool `json:"log,omitempty"` //If true, log this rule
|
||||
}
|
||||
|
||||
var MinStagingSpacesVersion *semver.Version = getMinStagingSpacesVersion()
|
||||
|
||||
func (c *Client) ListSecGroups() (secGroups []SecGroup, err error) {
|
||||
requestURL := "/v2/security_groups?inline-relations-depth=1"
|
||||
for requestURL != "" {
|
||||
var secGroupResp SecGroupResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting sec groups")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading sec group response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &secGroupResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling sec group")
|
||||
}
|
||||
|
||||
for _, secGroup := range secGroupResp.Resources {
|
||||
secGroup.Entity.Guid = secGroup.Meta.Guid
|
||||
secGroup.Entity.CreatedAt = secGroup.Meta.CreatedAt
|
||||
secGroup.Entity.UpdatedAt = secGroup.Meta.UpdatedAt
|
||||
secGroup.Entity.c = c
|
||||
for i, space := range secGroup.Entity.SpacesData {
|
||||
space.Entity.Guid = space.Meta.Guid
|
||||
secGroup.Entity.SpacesData[i] = space
|
||||
}
|
||||
if len(secGroup.Entity.SpacesData) == 0 {
|
||||
spaces, err := secGroup.Entity.ListSpaceResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, space := range spaces {
|
||||
secGroup.Entity.SpacesData = append(secGroup.Entity.SpacesData, space)
|
||||
}
|
||||
}
|
||||
if len(secGroup.Entity.StagingSpacesData) == 0 {
|
||||
spaces, err := secGroup.Entity.ListStagingSpaceResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, space := range spaces {
|
||||
secGroup.Entity.StagingSpacesData = append(secGroup.Entity.SpacesData, space)
|
||||
}
|
||||
}
|
||||
secGroups = append(secGroups, secGroup.Entity)
|
||||
}
|
||||
|
||||
requestURL = secGroupResp.NextUrl
|
||||
resp.Body.Close()
|
||||
}
|
||||
return secGroups, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListRunningSecGroups() ([]SecGroup, error) {
|
||||
secGroups := make([]SecGroup, 0)
|
||||
requestURL := "/v2/config/running_security_groups"
|
||||
for requestURL != "" {
|
||||
var secGroupResp SecGroupResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting sec groups")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading sec group response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &secGroupResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling sec group")
|
||||
}
|
||||
|
||||
for _, secGroup := range secGroupResp.Resources {
|
||||
secGroup.Entity.Guid = secGroup.Meta.Guid
|
||||
secGroup.Entity.CreatedAt = secGroup.Meta.CreatedAt
|
||||
secGroup.Entity.UpdatedAt = secGroup.Meta.UpdatedAt
|
||||
secGroup.Entity.c = c
|
||||
|
||||
secGroups = append(secGroups, secGroup.Entity)
|
||||
}
|
||||
|
||||
requestURL = secGroupResp.NextUrl
|
||||
resp.Body.Close()
|
||||
}
|
||||
return secGroups, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListStagingSecGroups() ([]SecGroup, error) {
|
||||
secGroups := make([]SecGroup, 0)
|
||||
requestURL := "/v2/config/staging_security_groups"
|
||||
for requestURL != "" {
|
||||
var secGroupResp SecGroupResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting sec groups")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading sec group response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &secGroupResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling sec group")
|
||||
}
|
||||
|
||||
for _, secGroup := range secGroupResp.Resources {
|
||||
secGroup.Entity.Guid = secGroup.Meta.Guid
|
||||
secGroup.Entity.CreatedAt = secGroup.Meta.CreatedAt
|
||||
secGroup.Entity.UpdatedAt = secGroup.Meta.UpdatedAt
|
||||
secGroup.Entity.c = c
|
||||
|
||||
secGroups = append(secGroups, secGroup.Entity)
|
||||
}
|
||||
|
||||
requestURL = secGroupResp.NextUrl
|
||||
resp.Body.Close()
|
||||
}
|
||||
return secGroups, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSecGroupByName(name string) (secGroup SecGroup, err error) {
|
||||
requestURL := "/v2/security_groups?q=name:" + name
|
||||
var secGroupResp SecGroupResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
|
||||
if err != nil {
|
||||
return secGroup, errors.Wrap(err, "Error requesting sec groups")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return secGroup, errors.Wrap(err, "Error reading sec group response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &secGroupResp)
|
||||
if err != nil {
|
||||
return secGroup, errors.Wrap(err, "Error unmarshaling sec group")
|
||||
}
|
||||
if len(secGroupResp.Resources) == 0 {
|
||||
return secGroup, fmt.Errorf("No security group with name %v found", name)
|
||||
}
|
||||
secGroup = secGroupResp.Resources[0].Entity
|
||||
secGroup.Guid = secGroupResp.Resources[0].Meta.Guid
|
||||
secGroup.CreatedAt = secGroupResp.Resources[0].Meta.CreatedAt
|
||||
secGroup.UpdatedAt = secGroupResp.Resources[0].Meta.UpdatedAt
|
||||
secGroup.c = c
|
||||
|
||||
resp.Body.Close()
|
||||
return secGroup, nil
|
||||
}
|
||||
|
||||
func (secGroup *SecGroup) ListSpaceResources() ([]SpaceResource, error) {
|
||||
var spaceResources []SpaceResource
|
||||
requestURL := secGroup.SpacesURL
|
||||
for requestURL != "" {
|
||||
spaceResp, err := secGroup.c.getSpaceResponse(requestURL)
|
||||
if err != nil {
|
||||
return []SpaceResource{}, err
|
||||
}
|
||||
for i, spaceRes := range spaceResp.Resources {
|
||||
spaceRes.Entity.Guid = spaceRes.Meta.Guid
|
||||
spaceRes.Entity.CreatedAt = spaceRes.Meta.CreatedAt
|
||||
spaceRes.Entity.UpdatedAt = spaceRes.Meta.UpdatedAt
|
||||
spaceResp.Resources[i] = spaceRes
|
||||
}
|
||||
spaceResources = append(spaceResources, spaceResp.Resources...)
|
||||
requestURL = spaceResp.NextUrl
|
||||
}
|
||||
return spaceResources, nil
|
||||
}
|
||||
|
||||
func (secGroup *SecGroup) ListStagingSpaceResources() ([]SpaceResource, error) {
|
||||
var spaceResources []SpaceResource
|
||||
requestURL := secGroup.StagingSpacesURL
|
||||
for requestURL != "" {
|
||||
spaceResp, err := secGroup.c.getSpaceResponse(requestURL)
|
||||
if err != nil {
|
||||
// if this is a 404, let's make sure that it's not because we're on a legacy system
|
||||
if cause := errors.Cause(err); cause != nil {
|
||||
if httpErr, ok := cause.(CloudFoundryHTTPError); ok {
|
||||
if httpErr.StatusCode == 404 {
|
||||
info, infoErr := secGroup.c.GetInfo()
|
||||
if infoErr != nil {
|
||||
return nil, infoErr
|
||||
}
|
||||
|
||||
apiVersion, versionErr := semver.NewVersion(info.APIVersion)
|
||||
if versionErr != nil {
|
||||
return nil, versionErr
|
||||
}
|
||||
|
||||
if MinStagingSpacesVersion.GreaterThan(apiVersion) {
|
||||
// this is probably not really an error, we're just trying to use a non-existent api
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []SpaceResource{}, err
|
||||
}
|
||||
for i, spaceRes := range spaceResp.Resources {
|
||||
spaceRes.Entity.Guid = spaceRes.Meta.Guid
|
||||
spaceRes.Entity.CreatedAt = spaceRes.Meta.CreatedAt
|
||||
spaceRes.Entity.UpdatedAt = spaceRes.Meta.UpdatedAt
|
||||
spaceResp.Resources[i] = spaceRes
|
||||
}
|
||||
spaceResources = append(spaceResources, spaceResp.Resources...)
|
||||
requestURL = spaceResp.NextUrl
|
||||
}
|
||||
return spaceResources, nil
|
||||
}
|
||||
|
||||
/*
|
||||
CreateSecGroup contacts the CF endpoint for creating a new security group.
|
||||
name: the name to give to the created security group
|
||||
rules: A slice of rule objects that describe the rules that this security group enforces.
|
||||
This can technically be nil or an empty slice - we won't judge you
|
||||
spaceGuids: The security group will be associated with the spaces specified by the contents of this slice.
|
||||
If nil, the security group will not be associated with any spaces initially.
|
||||
*/
|
||||
func (c *Client) CreateSecGroup(name string, rules []SecGroupRule, spaceGuids []string) (*SecGroup, error) {
|
||||
return c.secGroupCreateHelper("/v2/security_groups", "POST", name, rules, spaceGuids)
|
||||
}
|
||||
|
||||
/*
|
||||
UpdateSecGroup contacts the CF endpoint to update an existing security group.
|
||||
guid: identifies the security group that you would like to update.
|
||||
name: the new name to give to the security group
|
||||
rules: A slice of rule objects that describe the rules that this security group enforces.
|
||||
If this is left nil, the rules will not be changed.
|
||||
spaceGuids: The security group will be associated with the spaces specified by the contents of this slice.
|
||||
If nil, the space associations will not be changed.
|
||||
*/
|
||||
func (c *Client) UpdateSecGroup(guid, name string, rules []SecGroupRule, spaceGuids []string) (*SecGroup, error) {
|
||||
return c.secGroupCreateHelper("/v2/security_groups/"+guid, "PUT", name, rules, spaceGuids)
|
||||
}
|
||||
|
||||
/*
|
||||
DeleteSecGroup contacts the CF endpoint to delete an existing security group.
|
||||
guid: Indentifies the security group to be deleted.
|
||||
*/
|
||||
func (c *Client) DeleteSecGroup(guid string) error {
|
||||
//Perform the DELETE and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/security_groups/%s", guid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 204 { //204 No Content
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
GetSecGroup contacts the CF endpoint for fetching the info for a particular security group.
|
||||
guid: Identifies the security group to fetch information from
|
||||
*/
|
||||
func (c *Client) GetSecGroup(guid string) (*SecGroup, error) {
|
||||
//Perform the GET and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("GET", "/v2/security_groups/"+guid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
//get the json out of the response body
|
||||
return respBodyToSecGroup(resp.Body, c)
|
||||
}
|
||||
|
||||
/*
|
||||
BindSecGroup contacts the CF endpoint to associate a space with a security group
|
||||
secGUID: identifies the security group to add a space to
|
||||
spaceGUID: identifies the space to associate
|
||||
*/
|
||||
func (c *Client) BindSecGroup(secGUID, spaceGUID string) error {
|
||||
//Perform the PUT and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/security_groups/%s/spaces/%s", secGUID, spaceGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 201 { //201 Created
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
BindSpaceStagingSecGroup contacts the CF endpoint to associate a space with a security group for staging functions only
|
||||
secGUID: identifies the security group to add a space to
|
||||
spaceGUID: identifies the space to associate
|
||||
*/
|
||||
func (c *Client) BindStagingSecGroupToSpace(secGUID, spaceGUID string) error {
|
||||
//Perform the PUT and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/security_groups/%s/staging_spaces/%s", secGUID, spaceGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 201 { //201 Created
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
BindRunningSecGroup contacts the CF endpoint to associate a security group
|
||||
secGUID: identifies the security group to add a space to
|
||||
*/
|
||||
func (c *Client) BindRunningSecGroup(secGUID string) error {
|
||||
//Perform the PUT and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/config/running_security_groups/%s", secGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 200 { //200
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
UnbindRunningSecGroup contacts the CF endpoint to dis-associate a security group
|
||||
secGUID: identifies the security group to add a space to
|
||||
*/
|
||||
func (c *Client) UnbindRunningSecGroup(secGUID string) error {
|
||||
//Perform the DELETE and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/config/running_security_groups/%s", secGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent { //204
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
BindStagingSecGroup contacts the CF endpoint to associate a space with a security group
|
||||
secGUID: identifies the security group to add a space to
|
||||
*/
|
||||
func (c *Client) BindStagingSecGroup(secGUID string) error {
|
||||
//Perform the PUT and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/config/staging_security_groups/%s", secGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 200 { //200
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
UnbindStagingSecGroup contacts the CF endpoint to dis-associate a space with a security group
|
||||
secGUID: identifies the security group to add a space to
|
||||
*/
|
||||
func (c *Client) UnbindStagingSecGroup(secGUID string) error {
|
||||
//Perform the DELETE and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/config/staging_security_groups/%s", secGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent { //204
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
UnbindSecGroup contacts the CF endpoint to dissociate a space from a security group
|
||||
secGUID: identifies the security group to remove a space from
|
||||
spaceGUID: identifies the space to dissociate from the security group
|
||||
*/
|
||||
func (c *Client) UnbindSecGroup(secGUID, spaceGUID string) error {
|
||||
//Perform the DELETE and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/security_groups/%s/spaces/%s", secGUID, spaceGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 204 { //204 No Content
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//Reads most security group response bodies into a SecGroup object
|
||||
func respBodyToSecGroup(body io.ReadCloser, c *Client) (*SecGroup, error) {
|
||||
//get the json from the response body
|
||||
bodyRaw, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Could not read response body")
|
||||
}
|
||||
jStruct := SecGroupResource{}
|
||||
//make it a SecGroup
|
||||
err = json.Unmarshal(bodyRaw, &jStruct)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Could not unmarshal response body as json")
|
||||
}
|
||||
//pull a few extra fields from other places
|
||||
ret := jStruct.Entity
|
||||
ret.Guid = jStruct.Meta.Guid
|
||||
ret.CreatedAt = jStruct.Meta.CreatedAt
|
||||
ret.UpdatedAt = jStruct.Meta.UpdatedAt
|
||||
ret.c = c
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func convertStructToMap(st interface{}) map[string]interface{} {
|
||||
reqRules := make(map[string]interface{})
|
||||
|
||||
v := reflect.ValueOf(st)
|
||||
t := reflect.TypeOf(st)
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
key := strings.ToLower(t.Field(i).Name)
|
||||
typ := v.FieldByName(t.Field(i).Name).Kind().String()
|
||||
structTag := t.Field(i).Tag.Get("json")
|
||||
jsonName := strings.TrimSpace(strings.Split(structTag, ",")[0])
|
||||
value := v.FieldByName(t.Field(i).Name)
|
||||
|
||||
// if jsonName is not empty use it for the key
|
||||
if jsonName != "" {
|
||||
key = jsonName
|
||||
}
|
||||
|
||||
if typ == "string" {
|
||||
if !(value.String() == "" && strings.Contains(structTag, "omitempty")) {
|
||||
reqRules[key] = value.String()
|
||||
}
|
||||
} else if typ == "int" {
|
||||
reqRules[key] = value.Int()
|
||||
} else {
|
||||
reqRules[key] = value.Interface()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return reqRules
|
||||
}
|
||||
|
||||
//Create and Update secGroup pretty much do the same thing, so this function abstracts those out.
|
||||
func (c *Client) secGroupCreateHelper(url, method, name string, rules []SecGroupRule, spaceGuids []string) (*SecGroup, error) {
|
||||
reqRules := make([]map[string]interface{}, len(rules))
|
||||
|
||||
for i, rule := range rules {
|
||||
reqRules[i] = convertStructToMap(rule)
|
||||
protocol := strings.ToLower(reqRules[i]["protocol"].(string))
|
||||
|
||||
// if not icmp protocol need to remove the Code/Type fields
|
||||
if protocol != "icmp" {
|
||||
delete(reqRules[i], "code")
|
||||
delete(reqRules[i], "type")
|
||||
}
|
||||
}
|
||||
|
||||
req := c.NewRequest(method, url)
|
||||
//set up request body
|
||||
inputs := map[string]interface{}{
|
||||
"name": name,
|
||||
"rules": reqRules,
|
||||
}
|
||||
|
||||
if spaceGuids != nil {
|
||||
inputs["space_guids"] = spaceGuids
|
||||
}
|
||||
req.obj = inputs
|
||||
//fire off the request and check for problems
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 201 { // Both create and update should give 201 CREATED
|
||||
var response SecGroupCreateResponse
|
||||
|
||||
bodyRaw, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
err = json.Unmarshal(bodyRaw, &response)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling response")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`Request failed CF API returned with status code %d
|
||||
-------------------------------
|
||||
Error Code %s
|
||||
Code %d
|
||||
Description %s`,
|
||||
resp.StatusCode, response.ErrorCode, response.Code, response.Description)
|
||||
}
|
||||
//get the json from the response body
|
||||
return respBodyToSecGroup(resp.Body, c)
|
||||
}
|
||||
|
||||
func getMinStagingSpacesVersion() *semver.Version {
|
||||
v, _ := semver.NewVersion("2.68.0")
|
||||
return v
|
||||
}
|
176
vendor/github.com/cloudfoundry-community/go-cfclient/service_bindings.go
generated
vendored
Normal file
176
vendor/github.com/cloudfoundry-community/go-cfclient/service_bindings.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServiceBindingsResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
Resources []ServiceBindingResource `json:"resources"`
|
||||
NextUrl string `json:"next_url"`
|
||||
}
|
||||
|
||||
type ServiceBindingResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity ServiceBinding `json:"entity"`
|
||||
}
|
||||
|
||||
type ServiceBinding struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
AppGuid string `json:"app_guid"`
|
||||
ServiceInstanceGuid string `json:"service_instance_guid"`
|
||||
Credentials interface{} `json:"credentials"`
|
||||
BindingOptions interface{} `json:"binding_options"`
|
||||
GatewayData interface{} `json:"gateway_data"`
|
||||
GatewayName string `json:"gateway_name"`
|
||||
SyslogDrainUrl string `json:"syslog_drain_url"`
|
||||
VolumeMounts interface{} `json:"volume_mounts"`
|
||||
AppUrl string `json:"app_url"`
|
||||
ServiceInstanceUrl string `json:"service_instance_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceBindingsByQuery(query url.Values) ([]ServiceBinding, error) {
|
||||
var serviceBindings []ServiceBinding
|
||||
requestUrl := "/v2/service_bindings?" + query.Encode()
|
||||
|
||||
for {
|
||||
var serviceBindingsResp ServiceBindingsResponse
|
||||
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting service bindings")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading service bindings request:")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &serviceBindingsResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling service bindings")
|
||||
}
|
||||
for _, serviceBinding := range serviceBindingsResp.Resources {
|
||||
serviceBinding.Entity.Guid = serviceBinding.Meta.Guid
|
||||
serviceBinding.Entity.CreatedAt = serviceBinding.Meta.CreatedAt
|
||||
serviceBinding.Entity.UpdatedAt = serviceBinding.Meta.UpdatedAt
|
||||
serviceBinding.Entity.c = c
|
||||
serviceBindings = append(serviceBindings, serviceBinding.Entity)
|
||||
}
|
||||
requestUrl = serviceBindingsResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return serviceBindings, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceBindings() ([]ServiceBinding, error) {
|
||||
return c.ListServiceBindingsByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceBindingByGuid(guid string) (ServiceBinding, error) {
|
||||
var serviceBinding ServiceBindingResource
|
||||
r := c.NewRequest("GET", "/v2/service_bindings/"+url.QueryEscape(guid))
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return ServiceBinding{}, errors.Wrap(err, "Error requesting serving binding")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ServiceBinding{}, errors.Wrap(err, "Error reading service binding response body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &serviceBinding)
|
||||
if err != nil {
|
||||
return ServiceBinding{}, errors.Wrap(err, "Error unmarshalling service binding")
|
||||
}
|
||||
serviceBinding.Entity.Guid = serviceBinding.Meta.Guid
|
||||
serviceBinding.Entity.CreatedAt = serviceBinding.Meta.CreatedAt
|
||||
serviceBinding.Entity.UpdatedAt = serviceBinding.Meta.UpdatedAt
|
||||
serviceBinding.Entity.c = c
|
||||
return serviceBinding.Entity, nil
|
||||
}
|
||||
|
||||
func (c *Client) ServiceBindingByGuid(guid string) (ServiceBinding, error) {
|
||||
return c.GetServiceBindingByGuid(guid)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteServiceBinding(guid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/service_bindings/%s", guid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting service binding %s, response code %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateServiceBinding(appGUID, serviceInstanceGUID string) (*ServiceBinding, error) {
|
||||
req := c.NewRequest("POST", fmt.Sprintf("/v2/service_bindings"))
|
||||
req.obj = map[string]interface{}{
|
||||
"app_guid": appGUID,
|
||||
"service_instance_guid": serviceInstanceGUID,
|
||||
}
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, errors.Wrapf(err, "Error binding app %s to service instance %s, response code %d", appGUID, serviceInstanceGUID, resp.StatusCode)
|
||||
}
|
||||
return c.handleServiceBindingResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) CreateRouteServiceBinding(routeGUID, serviceInstanceGUID string) error {
|
||||
req := c.NewRequest("PUT", fmt.Sprintf("/v2/user_provided_service_instances/%s/routes/%s", serviceInstanceGUID, routeGUID))
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return errors.Wrapf(err, "Error binding route %s to service instance %s, response code %d", routeGUID, serviceInstanceGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteRouteServiceBinding(routeGUID, serviceInstanceGUID string) error {
|
||||
req := c.NewRequest("DELETE", fmt.Sprintf("/v2/service_instances/%s/routes/%s", serviceInstanceGUID, routeGUID))
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.Wrapf(err, "Error deleting bound route %s from service instance %s, response code %d", routeGUID, serviceInstanceGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleServiceBindingResp(resp *http.Response) (*ServiceBinding, error) {
|
||||
defer resp.Body.Close()
|
||||
var sb ServiceBindingResource
|
||||
err := json.NewDecoder(resp.Body).Decode(&sb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.mergeServiceBindingResource(sb), nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeServiceBindingResource(serviceBinding ServiceBindingResource) *ServiceBinding {
|
||||
serviceBinding.Entity.Guid = serviceBinding.Meta.Guid
|
||||
serviceBinding.Entity.c = c
|
||||
return &serviceBinding.Entity
|
||||
}
|
207
vendor/github.com/cloudfoundry-community/go-cfclient/service_brokers.go
generated
vendored
Normal file
207
vendor/github.com/cloudfoundry-community/go-cfclient/service_brokers.go
generated
vendored
Normal file
|
@ -0,0 +1,207 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServiceBrokerResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []ServiceBrokerResource `json:"resources"`
|
||||
}
|
||||
|
||||
type ServiceBrokerResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity ServiceBroker `json:"entity"`
|
||||
}
|
||||
|
||||
type UpdateServiceBrokerRequest struct {
|
||||
Name string `json:"name"`
|
||||
BrokerURL string `json:"broker_url"`
|
||||
Username string `json:"auth_username"`
|
||||
Password string `json:"auth_password"`
|
||||
}
|
||||
|
||||
type CreateServiceBrokerRequest struct {
|
||||
Name string `json:"name"`
|
||||
BrokerURL string `json:"broker_url"`
|
||||
Username string `json:"auth_username"`
|
||||
Password string `json:"auth_password"`
|
||||
SpaceGUID string `json:"space_guid,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceBroker struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
BrokerURL string `json:"broker_url"`
|
||||
Username string `json:"auth_username"`
|
||||
Password string `json:"auth_password"`
|
||||
SpaceGUID string `json:"space_guid,omitempty"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) DeleteServiceBroker(guid string) error {
|
||||
requestUrl := fmt.Sprintf("/v2/service_brokers/%s", guid)
|
||||
r := c.NewRequest("DELETE", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleteing service broker %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) UpdateServiceBroker(guid string, usb UpdateServiceBrokerRequest) (ServiceBroker, error) {
|
||||
var serviceBrokerResource ServiceBrokerResource
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(usb)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
req := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/service_brokers/%s", guid), buf)
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ServiceBroker{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &serviceBrokerResource)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
serviceBrokerResource.Entity.Guid = serviceBrokerResource.Meta.Guid
|
||||
return serviceBrokerResource.Entity, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateServiceBroker(csb CreateServiceBrokerRequest) (ServiceBroker, error) {
|
||||
var serviceBrokerResource ServiceBrokerResource
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(csb)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
req := c.NewRequestWithBody("POST", "/v2/service_brokers", buf)
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return ServiceBroker{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &serviceBrokerResource)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
|
||||
serviceBrokerResource.Entity.Guid = serviceBrokerResource.Meta.Guid
|
||||
return serviceBrokerResource.Entity, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceBrokersByQuery(query url.Values) ([]ServiceBroker, error) {
|
||||
var sbs []ServiceBroker
|
||||
requestUrl := "/v2/service_brokers?" + query.Encode()
|
||||
for {
|
||||
serviceBrokerResp, err := c.getServiceBrokerResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []ServiceBroker{}, err
|
||||
}
|
||||
for _, sb := range serviceBrokerResp.Resources {
|
||||
sb.Entity.Guid = sb.Meta.Guid
|
||||
sb.Entity.CreatedAt = sb.Meta.CreatedAt
|
||||
sb.Entity.UpdatedAt = sb.Meta.UpdatedAt
|
||||
sbs = append(sbs, sb.Entity)
|
||||
}
|
||||
requestUrl = serviceBrokerResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return sbs, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceBrokers() ([]ServiceBroker, error) {
|
||||
return c.ListServiceBrokersByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceBrokerByGuid(guid string) (ServiceBroker, error) {
|
||||
var serviceBrokerRes ServiceBrokerResource
|
||||
r := c.NewRequest("GET", "/v2/service_brokers/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &serviceBrokerRes)
|
||||
if err != nil {
|
||||
return ServiceBroker{}, err
|
||||
}
|
||||
serviceBrokerRes.Entity.Guid = serviceBrokerRes.Meta.Guid
|
||||
serviceBrokerRes.Entity.CreatedAt = serviceBrokerRes.Meta.CreatedAt
|
||||
serviceBrokerRes.Entity.UpdatedAt = serviceBrokerRes.Meta.UpdatedAt
|
||||
return serviceBrokerRes.Entity, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceBrokerByName(name string) (ServiceBroker, error) {
|
||||
var sb ServiceBroker
|
||||
q := url.Values{}
|
||||
q.Set("q", "name:"+name)
|
||||
sbs, err := c.ListServiceBrokersByQuery(q)
|
||||
if err != nil {
|
||||
return sb, err
|
||||
}
|
||||
if len(sbs) == 0 {
|
||||
return sb, fmt.Errorf("Unable to find service broker %s", name)
|
||||
}
|
||||
return sbs[0], nil
|
||||
}
|
||||
|
||||
func (c *Client) getServiceBrokerResponse(requestUrl string) (ServiceBrokerResponse, error) {
|
||||
var serviceBrokerResp ServiceBrokerResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return ServiceBrokerResponse{}, errors.Wrap(err, "Error requesting Service Brokers")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return ServiceBrokerResponse{}, errors.Wrap(err, "Error reading Service Broker request")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &serviceBrokerResp)
|
||||
if err != nil {
|
||||
return ServiceBrokerResponse{}, errors.Wrap(err, "Error unmarshalling Service Broker")
|
||||
}
|
||||
return serviceBrokerResp, nil
|
||||
}
|
186
vendor/github.com/cloudfoundry-community/go-cfclient/service_instances.go
generated
vendored
Normal file
186
vendor/github.com/cloudfoundry-community/go-cfclient/service_instances.go
generated
vendored
Normal file
|
@ -0,0 +1,186 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServiceInstancesResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []ServiceInstanceResource `json:"resources"`
|
||||
}
|
||||
|
||||
type ServiceInstanceRequest struct {
|
||||
Name string `json:"name"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
ServicePlanGuid string `json:"service_plan_guid"`
|
||||
Parameters map[string]interface{} `json:"parameters,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceInstanceResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity ServiceInstance `json:"entity"`
|
||||
}
|
||||
|
||||
type ServiceInstance struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Credentials map[string]interface{} `json:"credentials"`
|
||||
ServicePlanGuid string `json:"service_plan_guid"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
DashboardUrl string `json:"dashboard_url"`
|
||||
Type string `json:"type"`
|
||||
LastOperation LastOperation `json:"last_operation"`
|
||||
Tags []string `json:"tags"`
|
||||
ServiceGuid string `json:"service_guid"`
|
||||
SpaceUrl string `json:"space_url"`
|
||||
ServicePlanUrl string `json:"service_plan_url"`
|
||||
ServiceBindingsUrl string `json:"service_bindings_url"`
|
||||
ServiceKeysUrl string `json:"service_keys_url"`
|
||||
RoutesUrl string `json:"routes_url"`
|
||||
ServiceUrl string `json:"service_url"`
|
||||
Guid string `json:"guid"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type LastOperation struct {
|
||||
Type string `json:"type"`
|
||||
State string `json:"state"`
|
||||
Description string `json:"description"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceInstancesByQuery(query url.Values) ([]ServiceInstance, error) {
|
||||
var instances []ServiceInstance
|
||||
|
||||
requestUrl := "/v2/service_instances?" + query.Encode()
|
||||
for {
|
||||
var sir ServiceInstancesResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting service instances")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading service instances request:")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &sir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling service instances")
|
||||
}
|
||||
for _, instance := range sir.Resources {
|
||||
instances = append(instances, c.mergeServiceInstance(instance))
|
||||
}
|
||||
|
||||
requestUrl = sir.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceInstances() ([]ServiceInstance, error) {
|
||||
return c.ListServiceInstancesByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceInstanceByGuid(guid string) (ServiceInstance, error) {
|
||||
var sir ServiceInstanceResource
|
||||
req := c.NewRequest("GET", "/v2/service_instances/"+guid)
|
||||
res, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return ServiceInstance{}, errors.Wrap(err, "Error requesting service instance")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return ServiceInstance{}, errors.Wrap(err, "Error reading service instance response")
|
||||
}
|
||||
err = json.Unmarshal(data, &sir)
|
||||
if err != nil {
|
||||
return ServiceInstance{}, errors.Wrap(err, "Error JSON parsing service instance response")
|
||||
}
|
||||
return c.mergeServiceInstance(sir), nil
|
||||
}
|
||||
|
||||
func (c *Client) ServiceInstanceByGuid(guid string) (ServiceInstance, error) {
|
||||
return c.GetServiceInstanceByGuid(guid)
|
||||
}
|
||||
|
||||
func (c *Client) mergeServiceInstance(instance ServiceInstanceResource) ServiceInstance {
|
||||
instance.Entity.Guid = instance.Meta.Guid
|
||||
instance.Entity.CreatedAt = instance.Meta.CreatedAt
|
||||
instance.Entity.UpdatedAt = instance.Meta.UpdatedAt
|
||||
instance.Entity.c = c
|
||||
return instance.Entity
|
||||
}
|
||||
|
||||
func (c *Client) CreateServiceInstance(req ServiceInstanceRequest) (ServiceInstance, error) {
|
||||
var sir ServiceInstanceResource
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return ServiceInstance{}, err
|
||||
}
|
||||
|
||||
r := c.NewRequestWithBody("POST", "/v2/service_instances?accepts_incomplete=true", buf)
|
||||
|
||||
res, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return ServiceInstance{}, err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusAccepted && res.StatusCode != http.StatusCreated {
|
||||
return ServiceInstance{}, errors.Wrapf(err, "Error creating service, response code: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return ServiceInstance{}, errors.Wrap(err, "Error reading service instance response")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &sir)
|
||||
if err != nil {
|
||||
return ServiceInstance{}, errors.Wrap(err, "Error JSON parsing service instance response")
|
||||
}
|
||||
|
||||
return c.mergeServiceInstance(sir), nil
|
||||
}
|
||||
|
||||
func (c *Client) UpdateServiceInstance(serviceInstanceGuid string, updatedConfiguration io.Reader, async bool) error {
|
||||
u := fmt.Sprintf("/v2/service_instances/%s?accepts_incomplete=%t", serviceInstanceGuid, async)
|
||||
resp, err := c.DoRequest(c.NewRequestWithBody("PUT", u, updatedConfiguration))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
return errors.Wrapf(err, "Error updating service instance %s, response code %d", serviceInstanceGuid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteServiceInstance(guid string, recursive, async bool) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/service_instances/%s?recursive=%t&accepts_incomplete=%t&async=%t", guid, recursive, async, async)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
return errors.Wrapf(err, "Error deleting service instance %s, response code %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
171
vendor/github.com/cloudfoundry-community/go-cfclient/service_keys.go
generated
vendored
Normal file
171
vendor/github.com/cloudfoundry-community/go-cfclient/service_keys.go
generated
vendored
Normal file
|
@ -0,0 +1,171 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServiceKeysResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
Resources []ServiceKeyResource `json:"resources"`
|
||||
NextUrl string `json:"next_url"`
|
||||
}
|
||||
|
||||
type ServiceKeyResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity ServiceKey `json:"entity"`
|
||||
}
|
||||
|
||||
type CreateServiceKeyRequest struct {
|
||||
Name string `json:"name"`
|
||||
ServiceInstanceGuid string `json:"service_instance_guid"`
|
||||
Parameters interface{} `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceKey struct {
|
||||
Name string `json:"name"`
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
ServiceInstanceGuid string `json:"service_instance_guid"`
|
||||
Credentials interface{} `json:"credentials"`
|
||||
ServiceInstanceUrl string `json:"service_instance_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceKeysByQuery(query url.Values) ([]ServiceKey, error) {
|
||||
var serviceKeys []ServiceKey
|
||||
requestUrl := "/v2/service_keys?" + query.Encode()
|
||||
|
||||
for {
|
||||
var serviceKeysResp ServiceKeysResponse
|
||||
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting service keys")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading service keys request:")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &serviceKeysResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error unmarshaling service keys: %q", string(resBody))
|
||||
}
|
||||
for _, serviceKey := range serviceKeysResp.Resources {
|
||||
serviceKey.Entity.Guid = serviceKey.Meta.Guid
|
||||
serviceKey.Entity.CreatedAt = serviceKey.Meta.CreatedAt
|
||||
serviceKey.Entity.UpdatedAt = serviceKey.Meta.UpdatedAt
|
||||
serviceKey.Entity.c = c
|
||||
serviceKeys = append(serviceKeys, serviceKey.Entity)
|
||||
}
|
||||
|
||||
requestUrl = serviceKeysResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return serviceKeys, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServiceKeys() ([]ServiceKey, error) {
|
||||
return c.ListServiceKeysByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceKeyByName(name string) (ServiceKey, error) {
|
||||
var serviceKey ServiceKey
|
||||
q := url.Values{}
|
||||
q.Set("q", "name:"+name)
|
||||
serviceKeys, err := c.ListServiceKeysByQuery(q)
|
||||
if err != nil {
|
||||
return serviceKey, err
|
||||
}
|
||||
if len(serviceKeys) == 0 {
|
||||
return serviceKey, fmt.Errorf("Unable to find service key %s", name)
|
||||
}
|
||||
return serviceKeys[0], nil
|
||||
}
|
||||
|
||||
// GetServiceKeyByInstanceGuid is deprecated in favor of GetServiceKeysByInstanceGuid
|
||||
func (c *Client) GetServiceKeyByInstanceGuid(guid string) (ServiceKey, error) {
|
||||
var serviceKey ServiceKey
|
||||
q := url.Values{}
|
||||
q.Set("q", "service_instance_guid:"+guid)
|
||||
serviceKeys, err := c.ListServiceKeysByQuery(q)
|
||||
if err != nil {
|
||||
return serviceKey, err
|
||||
}
|
||||
if len(serviceKeys) == 0 {
|
||||
return serviceKey, fmt.Errorf("Unable to find service key for guid %s", guid)
|
||||
}
|
||||
return serviceKeys[0], nil
|
||||
}
|
||||
|
||||
// GetServiceKeysByInstanceGuid returns the service keys for a service instance.
|
||||
// If none are found, it returns an error.
|
||||
func (c *Client) GetServiceKeysByInstanceGuid(guid string) ([]ServiceKey, error) {
|
||||
q := url.Values{}
|
||||
q.Set("q", "service_instance_guid:"+guid)
|
||||
serviceKeys, err := c.ListServiceKeysByQuery(q)
|
||||
if err != nil {
|
||||
return serviceKeys, err
|
||||
}
|
||||
if len(serviceKeys) == 0 {
|
||||
return serviceKeys, fmt.Errorf("Unable to find service key for guid %s", guid)
|
||||
}
|
||||
return serviceKeys, nil
|
||||
}
|
||||
|
||||
// CreateServiceKey creates a service key from the request. If a service key
|
||||
// exists already, it returns an error containing `CF-ServiceKeyNameTaken`
|
||||
func (c *Client) CreateServiceKey(csr CreateServiceKeyRequest) (ServiceKey, error) {
|
||||
var serviceKeyResource ServiceKeyResource
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(csr)
|
||||
if err != nil {
|
||||
return ServiceKey{}, err
|
||||
}
|
||||
req := c.NewRequestWithBody("POST", "/v2/service_keys", buf)
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return ServiceKey{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return ServiceKey{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return ServiceKey{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &serviceKeyResource)
|
||||
if err != nil {
|
||||
return ServiceKey{}, err
|
||||
}
|
||||
|
||||
return serviceKeyResource.Entity, nil
|
||||
}
|
||||
|
||||
// DeleteServiceKey removes a service key instance
|
||||
func (c *Client) DeleteServiceKey(guid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/service_keys/%s", guid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting service instance key %s, response code %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
169
vendor/github.com/cloudfoundry-community/go-cfclient/service_plan_visibilities.go
generated
vendored
Normal file
169
vendor/github.com/cloudfoundry-community/go-cfclient/service_plan_visibilities.go
generated
vendored
Normal file
|
@ -0,0 +1,169 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServicePlanVisibilitiesResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []ServicePlanVisibilityResource `json:"resources"`
|
||||
}
|
||||
|
||||
type ServicePlanVisibilityResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity ServicePlanVisibility `json:"entity"`
|
||||
}
|
||||
|
||||
type ServicePlanVisibility struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
ServicePlanGuid string `json:"service_plan_guid"`
|
||||
OrganizationGuid string `json:"organization_guid"`
|
||||
ServicePlanUrl string `json:"service_plan_url"`
|
||||
OrganizationUrl string `json:"organization_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListServicePlanVisibilitiesByQuery(query url.Values) ([]ServicePlanVisibility, error) {
|
||||
var servicePlanVisibilities []ServicePlanVisibility
|
||||
requestUrl := "/v2/service_plan_visibilities?" + query.Encode()
|
||||
for {
|
||||
var servicePlanVisibilitiesResp ServicePlanVisibilitiesResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting service plan visibilities")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading service plan visibilities request:")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &servicePlanVisibilitiesResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling service plan visibilities")
|
||||
}
|
||||
for _, servicePlanVisibility := range servicePlanVisibilitiesResp.Resources {
|
||||
servicePlanVisibility.Entity.Guid = servicePlanVisibility.Meta.Guid
|
||||
servicePlanVisibility.Entity.CreatedAt = servicePlanVisibility.Meta.CreatedAt
|
||||
servicePlanVisibility.Entity.UpdatedAt = servicePlanVisibility.Meta.UpdatedAt
|
||||
servicePlanVisibility.Entity.c = c
|
||||
servicePlanVisibilities = append(servicePlanVisibilities, servicePlanVisibility.Entity)
|
||||
}
|
||||
requestUrl = servicePlanVisibilitiesResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return servicePlanVisibilities, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServicePlanVisibilities() ([]ServicePlanVisibility, error) {
|
||||
return c.ListServicePlanVisibilitiesByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetServicePlanVisibilityByGuid(guid string) (ServicePlanVisibility, error) {
|
||||
r := c.NewRequest("GET", "/v2/service_plan_visibilities/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return ServicePlanVisibility{}, err
|
||||
}
|
||||
return respBodyToServicePlanVisibility(resp.Body, c)
|
||||
}
|
||||
|
||||
//a uniqueID is the id of the service in the catalog and not in cf internal db
|
||||
func (c *Client) CreateServicePlanVisibilityByUniqueId(uniqueId string, organizationGuid string) (ServicePlanVisibility, error) {
|
||||
q := url.Values{}
|
||||
q.Set("q", fmt.Sprintf("unique_id:%s", uniqueId))
|
||||
plans, err := c.ListServicePlansByQuery(q)
|
||||
if err != nil {
|
||||
return ServicePlanVisibility{}, errors.Wrap(err, fmt.Sprintf("Couldn't find a service plan with unique_id: %s", uniqueId))
|
||||
}
|
||||
return c.CreateServicePlanVisibility(plans[0].Guid, organizationGuid)
|
||||
}
|
||||
|
||||
func (c *Client) CreateServicePlanVisibility(servicePlanGuid string, organizationGuid string) (ServicePlanVisibility, error) {
|
||||
req := c.NewRequest("POST", "/v2/service_plan_visibilities")
|
||||
req.obj = map[string]interface{}{
|
||||
"service_plan_guid": servicePlanGuid,
|
||||
"organization_guid": organizationGuid,
|
||||
}
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return ServicePlanVisibility{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return ServicePlanVisibility{}, errors.Wrapf(err, "Error creating service plan visibility, response code: %d", resp.StatusCode)
|
||||
}
|
||||
return respBodyToServicePlanVisibility(resp.Body, c)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteServicePlanVisibilityByPlanAndOrg(servicePlanGuid string, organizationGuid string, async bool) error {
|
||||
q := url.Values{}
|
||||
q.Set("q", fmt.Sprintf("organization_guid:%s;service_plan_guid:%s", organizationGuid, servicePlanGuid))
|
||||
plans, err := c.ListServicePlanVisibilitiesByQuery(q)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Couldn't find a service plan visibility for service plan %s and org %s", servicePlanGuid, organizationGuid))
|
||||
}
|
||||
if len(plans) != 1 {
|
||||
return fmt.Errorf("Query for a service plan visibility did not return exactly one result when searching for a service plan visibility for service plan %s and org %s",
|
||||
servicePlanGuid, organizationGuid)
|
||||
}
|
||||
return c.DeleteServicePlanVisibility(plans[0].Guid, async)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteServicePlanVisibility(guid string, async bool) error {
|
||||
req := c.NewRequest("DELETE", fmt.Sprintf("/v2/service_plan_visibilities/%s?async=%v", guid, async))
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting service plan visibility, response code: %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) UpdateServicePlanVisibility(guid string, servicePlanGuid string, organizationGuid string) (ServicePlanVisibility, error) {
|
||||
req := c.NewRequest("PUT", "/v2/service_plan_visibilities/"+guid)
|
||||
req.obj = map[string]interface{}{
|
||||
"service_plan_guid": servicePlanGuid,
|
||||
"organization_guid": organizationGuid,
|
||||
}
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return ServicePlanVisibility{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return ServicePlanVisibility{}, errors.Wrapf(err, "Error updating service plan visibility, response code: %d", resp.StatusCode)
|
||||
}
|
||||
return respBodyToServicePlanVisibility(resp.Body, c)
|
||||
}
|
||||
|
||||
func respBodyToServicePlanVisibility(body io.ReadCloser, c *Client) (ServicePlanVisibility, error) {
|
||||
bodyRaw, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return ServicePlanVisibility{}, err
|
||||
}
|
||||
servicePlanVisibilityRes := ServicePlanVisibilityResource{}
|
||||
err = json.Unmarshal(bodyRaw, &servicePlanVisibilityRes)
|
||||
if err != nil {
|
||||
return ServicePlanVisibility{}, err
|
||||
}
|
||||
servicePlanVisibility := servicePlanVisibilityRes.Entity
|
||||
servicePlanVisibility.Guid = servicePlanVisibilityRes.Meta.Guid
|
||||
servicePlanVisibility.CreatedAt = servicePlanVisibilityRes.Meta.CreatedAt
|
||||
servicePlanVisibility.UpdatedAt = servicePlanVisibilityRes.Meta.UpdatedAt
|
||||
servicePlanVisibility.c = c
|
||||
return servicePlanVisibility, nil
|
||||
}
|
129
vendor/github.com/cloudfoundry-community/go-cfclient/service_plans.go
generated
vendored
Normal file
129
vendor/github.com/cloudfoundry-community/go-cfclient/service_plans.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServicePlansResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []ServicePlanResource `json:"resources"`
|
||||
}
|
||||
|
||||
type ServicePlanResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity ServicePlan `json:"entity"`
|
||||
}
|
||||
|
||||
type ServicePlan struct {
|
||||
Name string `json:"name"`
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Free bool `json:"free"`
|
||||
Description string `json:"description"`
|
||||
ServiceGuid string `json:"service_guid"`
|
||||
Extra interface{} `json:"extra"`
|
||||
UniqueId string `json:"unique_id"`
|
||||
Public bool `json:"public"`
|
||||
Active bool `json:"active"`
|
||||
Bindable bool `json:"bindable"`
|
||||
ServiceUrl string `json:"service_url"`
|
||||
ServiceInstancesUrl string `json:"service_instances_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListServicePlansByQuery(query url.Values) ([]ServicePlan, error) {
|
||||
var servicePlans []ServicePlan
|
||||
requestUrl := "/v2/service_plans?" + query.Encode()
|
||||
for {
|
||||
var servicePlansResp ServicePlansResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting service plans")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading service plans request:")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &servicePlansResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling service plans")
|
||||
}
|
||||
for _, servicePlan := range servicePlansResp.Resources {
|
||||
servicePlan.Entity.Guid = servicePlan.Meta.Guid
|
||||
servicePlan.Entity.CreatedAt = servicePlan.Meta.CreatedAt
|
||||
servicePlan.Entity.UpdatedAt = servicePlan.Meta.UpdatedAt
|
||||
servicePlan.Entity.c = c
|
||||
servicePlans = append(servicePlans, servicePlan.Entity)
|
||||
}
|
||||
requestUrl = servicePlansResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return servicePlans, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServicePlans() ([]ServicePlan, error) {
|
||||
return c.ListServicePlansByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetServicePlanByGUID(guid string) (*ServicePlan, error) {
|
||||
var (
|
||||
plan *ServicePlan
|
||||
planResponse ServicePlanResource
|
||||
)
|
||||
|
||||
r := c.NewRequest("GET", "/v2/service_plans/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &planResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planResponse.Entity.Guid = planResponse.Meta.Guid
|
||||
planResponse.Entity.CreatedAt = planResponse.Meta.CreatedAt
|
||||
planResponse.Entity.UpdatedAt = planResponse.Meta.UpdatedAt
|
||||
plan = &planResponse.Entity
|
||||
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
func (c *Client) MakeServicePlanPublic(servicePlanGUID string) error {
|
||||
return c.setPlanGlobalVisibility(servicePlanGUID, true)
|
||||
}
|
||||
|
||||
func (c *Client) MakeServicePlanPrivate(servicePlanGUID string) error {
|
||||
return c.setPlanGlobalVisibility(servicePlanGUID, false)
|
||||
}
|
||||
|
||||
func (c *Client) setPlanGlobalVisibility(servicePlanGUID string, public bool) error {
|
||||
bodyString := fmt.Sprintf(`{"public": %t}`, public)
|
||||
req := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/service_plans/%s", servicePlanGUID), bytes.NewBufferString(bodyString))
|
||||
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return nil
|
||||
}
|
72
vendor/github.com/cloudfoundry-community/go-cfclient/service_usage_events.go
generated
vendored
Normal file
72
vendor/github.com/cloudfoundry-community/go-cfclient/service_usage_events.go
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServiceUsageEvent struct {
|
||||
GUID string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
State string `json:"state"`
|
||||
OrgGUID string `json:"org_guid"`
|
||||
SpaceGUID string `json:"space_guid"`
|
||||
SpaceName string `json:"space_name"`
|
||||
ServiceInstanceGUID string `json:"service_instance_guid"`
|
||||
ServiceInstanceName string `json:"service_instance_name"`
|
||||
ServiceInstanceType string `json:"service_instance_type"`
|
||||
ServicePlanGUID string `json:"service_plan_guid"`
|
||||
ServicePlanName string `json:"service_plan_name"`
|
||||
ServiceGUID string `json:"service_guid"`
|
||||
ServiceLabel string `json:"service_label"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type ServiceUsageEventsResponse struct {
|
||||
TotalResults int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextURL string `json:"next_url"`
|
||||
Resources []ServiceUsageEventResource `json:"resources"`
|
||||
}
|
||||
|
||||
type ServiceUsageEventResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity ServiceUsageEvent `json:"entity"`
|
||||
}
|
||||
|
||||
// ListServiceUsageEventsByQuery lists all events matching the provided query.
|
||||
func (c *Client) ListServiceUsageEventsByQuery(query url.Values) ([]ServiceUsageEvent, error) {
|
||||
var serviceUsageEvents []ServiceUsageEvent
|
||||
requestURL := fmt.Sprintf("/v2/service_usage_events?%s", query.Encode())
|
||||
for {
|
||||
var serviceUsageEventsResponse ServiceUsageEventsResponse
|
||||
r := c.NewRequest("GET", requestURL)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error requesting events")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if err := json.NewDecoder(resp.Body).Decode(&serviceUsageEventsResponse); err != nil {
|
||||
return nil, errors.Wrap(err, "error unmarshaling events")
|
||||
}
|
||||
for _, e := range serviceUsageEventsResponse.Resources {
|
||||
e.Entity.GUID = e.Meta.Guid
|
||||
e.Entity.CreatedAt = e.Meta.CreatedAt
|
||||
e.Entity.c = c
|
||||
serviceUsageEvents = append(serviceUsageEvents, e.Entity)
|
||||
}
|
||||
requestURL = serviceUsageEventsResponse.NextURL
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return serviceUsageEvents, nil
|
||||
}
|
||||
|
||||
// ListServiceUsageEvents lists all unfiltered events.
|
||||
func (c *Client) ListServiceUsageEvents() ([]ServiceUsageEvent, error) {
|
||||
return c.ListServiceUsageEventsByQuery(nil)
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServicesResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []ServicesResource `json:"resources"`
|
||||
}
|
||||
|
||||
type ServicesResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Service `json:"entity"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Guid string `json:"guid"`
|
||||
Label string `json:"label"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Description string `json:"description"`
|
||||
Active bool `json:"active"`
|
||||
Bindable bool `json:"bindable"`
|
||||
ServiceBrokerGuid string `json:"service_broker_guid"`
|
||||
PlanUpdateable bool `json:"plan_updateable"`
|
||||
Tags []string `json:"tags"`
|
||||
UniqueID string `json:"unique_id"`
|
||||
Extra string `json:"extra"`
|
||||
Requires []string `json:"requires"`
|
||||
InstancesRetrievable bool `json:"instances_retrievable"`
|
||||
BindingsRetrievable bool `json:"bindings_retrievable"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type ServiceSummary struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
BoundAppCount int `json:"bound_app_count"`
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceByGuid(guid string) (Service, error) {
|
||||
var serviceRes ServicesResource
|
||||
r := c.NewRequest("GET", "/v2/services/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Service{}, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return Service{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &serviceRes)
|
||||
if err != nil {
|
||||
return Service{}, err
|
||||
}
|
||||
serviceRes.Entity.Guid = serviceRes.Meta.Guid
|
||||
serviceRes.Entity.CreatedAt = serviceRes.Meta.CreatedAt
|
||||
serviceRes.Entity.UpdatedAt = serviceRes.Meta.UpdatedAt
|
||||
return serviceRes.Entity, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) ListServicesByQuery(query url.Values) ([]Service, error) {
|
||||
var services []Service
|
||||
requestUrl := "/v2/services?" + query.Encode()
|
||||
for {
|
||||
var serviceResp ServicesResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting services")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading services request:")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &serviceResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling services")
|
||||
}
|
||||
for _, service := range serviceResp.Resources {
|
||||
service.Entity.Guid = service.Meta.Guid
|
||||
service.Entity.CreatedAt = service.Meta.CreatedAt
|
||||
service.Entity.UpdatedAt = service.Meta.UpdatedAt
|
||||
service.Entity.c = c
|
||||
services = append(services, service.Entity)
|
||||
}
|
||||
requestUrl = serviceResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListServices() ([]Service, error) {
|
||||
return c.ListServicesByQuery(nil)
|
||||
}
|
183
vendor/github.com/cloudfoundry-community/go-cfclient/space_quotas.go
generated
vendored
Normal file
183
vendor/github.com/cloudfoundry-community/go-cfclient/space_quotas.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type SpaceQuotasResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []SpaceQuotasResource `json:"resources"`
|
||||
}
|
||||
|
||||
type SpaceQuotasResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity SpaceQuota `json:"entity"`
|
||||
}
|
||||
|
||||
type SpaceQuotaRequest struct {
|
||||
Name string `json:"name"`
|
||||
OrganizationGuid string `json:"organization_guid"`
|
||||
NonBasicServicesAllowed bool `json:"non_basic_services_allowed"`
|
||||
TotalServices int `json:"total_services"`
|
||||
TotalRoutes int `json:"total_routes"`
|
||||
MemoryLimit int `json:"memory_limit"`
|
||||
InstanceMemoryLimit int `json:"instance_memory_limit"`
|
||||
AppInstanceLimit int `json:"app_instance_limit"`
|
||||
AppTaskLimit int `json:"app_task_limit"`
|
||||
TotalServiceKeys int `json:"total_service_keys"`
|
||||
TotalReservedRoutePorts int `json:"total_reserved_route_ports"`
|
||||
}
|
||||
|
||||
type SpaceQuota struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
Name string `json:"name"`
|
||||
OrganizationGuid string `json:"organization_guid"`
|
||||
NonBasicServicesAllowed bool `json:"non_basic_services_allowed"`
|
||||
TotalServices int `json:"total_services"`
|
||||
TotalRoutes int `json:"total_routes"`
|
||||
MemoryLimit int `json:"memory_limit"`
|
||||
InstanceMemoryLimit int `json:"instance_memory_limit"`
|
||||
AppInstanceLimit int `json:"app_instance_limit"`
|
||||
AppTaskLimit int `json:"app_task_limit"`
|
||||
TotalServiceKeys int `json:"total_service_keys"`
|
||||
TotalReservedRoutePorts int `json:"total_reserved_route_ports"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceQuotasByQuery(query url.Values) ([]SpaceQuota, error) {
|
||||
var spaceQuotas []SpaceQuota
|
||||
requestUrl := "/v2/space_quota_definitions?" + query.Encode()
|
||||
for {
|
||||
spaceQuotasResp, err := c.getSpaceQuotasResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []SpaceQuota{}, err
|
||||
}
|
||||
for _, space := range spaceQuotasResp.Resources {
|
||||
space.Entity.Guid = space.Meta.Guid
|
||||
space.Entity.CreatedAt = space.Meta.CreatedAt
|
||||
space.Entity.UpdatedAt = space.Meta.UpdatedAt
|
||||
space.Entity.c = c
|
||||
spaceQuotas = append(spaceQuotas, space.Entity)
|
||||
}
|
||||
requestUrl = spaceQuotasResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return spaceQuotas, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceQuotas() ([]SpaceQuota, error) {
|
||||
return c.ListSpaceQuotasByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetSpaceQuotaByName(name string) (SpaceQuota, error) {
|
||||
q := url.Values{}
|
||||
q.Set("q", "name:"+name)
|
||||
spaceQuotas, err := c.ListSpaceQuotasByQuery(q)
|
||||
if err != nil {
|
||||
return SpaceQuota{}, err
|
||||
}
|
||||
if len(spaceQuotas) != 1 {
|
||||
return SpaceQuota{}, fmt.Errorf("Unable to find space quota " + name)
|
||||
}
|
||||
return spaceQuotas[0], nil
|
||||
}
|
||||
|
||||
func (c *Client) getSpaceQuotasResponse(requestUrl string) (SpaceQuotasResponse, error) {
|
||||
var spaceQuotasResp SpaceQuotasResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return SpaceQuotasResponse{}, errors.Wrap(err, "Error requesting space quotas")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return SpaceQuotasResponse{}, errors.Wrap(err, "Error reading space quotas body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &spaceQuotasResp)
|
||||
if err != nil {
|
||||
return SpaceQuotasResponse{}, errors.Wrap(err, "Error unmarshalling space quotas")
|
||||
}
|
||||
return spaceQuotasResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) AssignSpaceQuota(quotaGUID, spaceGUID string) error {
|
||||
//Perform the PUT and check for errors
|
||||
resp, err := c.DoRequest(c.NewRequest("PUT", fmt.Sprintf("/v2/space_quota_definitions/%s/spaces/%s", quotaGUID, spaceGUID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated { //201
|
||||
return fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateSpaceQuota(spaceQuote SpaceQuotaRequest) (*SpaceQuota, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(spaceQuote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/space_quota_definitions", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleSpaceQuotaResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateSpaceQuota(spaceQuotaGUID string, spaceQuote SpaceQuotaRequest) (*SpaceQuota, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(spaceQuote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/space_quota_definitions/%s", spaceQuotaGUID), buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleSpaceQuotaResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) handleSpaceQuotaResp(resp *http.Response) (*SpaceQuota, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var spaceQuotasResource SpaceQuotasResource
|
||||
err = json.Unmarshal(body, &spaceQuotasResource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.mergeSpaceQuotaResource(spaceQuotasResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeSpaceQuotaResource(spaceQuote SpaceQuotasResource) *SpaceQuota {
|
||||
spaceQuote.Entity.Guid = spaceQuote.Meta.Guid
|
||||
spaceQuote.Entity.CreatedAt = spaceQuote.Meta.CreatedAt
|
||||
spaceQuote.Entity.UpdatedAt = spaceQuote.Meta.UpdatedAt
|
||||
spaceQuote.Entity.c = c
|
||||
return &spaceQuote.Entity
|
||||
}
|
|
@ -0,0 +1,790 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type SpaceRequest struct {
|
||||
Name string `json:"name"`
|
||||
OrganizationGuid string `json:"organization_guid"`
|
||||
DeveloperGuid []string `json:"developer_guids,omitempty"`
|
||||
ManagerGuid []string `json:"manager_guids,omitempty"`
|
||||
AuditorGuid []string `json:"auditor_guids,omitempty"`
|
||||
DomainGuid []string `json:"domain_guids,omitempty"`
|
||||
SecurityGroupGuids []string `json:"security_group_guids,omitempty"`
|
||||
SpaceQuotaDefGuid string `json:"space_quota_definition_guid,omitempty"`
|
||||
IsolationSegmentGuid string `json:"isolation_segment_guid,omitempty"`
|
||||
AllowSSH bool `json:"allow_ssh"`
|
||||
}
|
||||
|
||||
type SpaceResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []SpaceResource `json:"resources"`
|
||||
}
|
||||
|
||||
type SpaceResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Space `json:"entity"`
|
||||
}
|
||||
|
||||
type ServicePlanEntity struct {
|
||||
Name string `json:"name"`
|
||||
Free bool `json:"free"`
|
||||
Public bool `json:"public"`
|
||||
Active bool `json:"active"`
|
||||
Description string `json:"description"`
|
||||
ServiceOfferingGUID string `json:"service_guid"`
|
||||
ServiceOffering ServiceOfferingResource `json:"service"`
|
||||
}
|
||||
|
||||
type ServiceOfferingExtra struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
DocumentationURL string `json:"documentationURL"`
|
||||
LongDescription string `json:"longDescription"`
|
||||
}
|
||||
|
||||
type ServiceOfferingEntity struct {
|
||||
Label string
|
||||
Description string
|
||||
Provider string `json:"provider"`
|
||||
BrokerGUID string `json:"service_broker_guid"`
|
||||
Requires []string `json:"requires"`
|
||||
ServicePlans []interface{} `json:"service_plans"`
|
||||
Extra ServiceOfferingExtra
|
||||
}
|
||||
|
||||
type ServiceOfferingResource struct {
|
||||
Metadata Meta
|
||||
Entity ServiceOfferingEntity
|
||||
}
|
||||
|
||||
type ServiceOfferingResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
PrevUrl string `json:"prev_url"`
|
||||
Resources []ServiceOfferingResource `json:"resources"`
|
||||
}
|
||||
|
||||
type SpaceUserResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextURL string `json:"next_url"`
|
||||
Resources []UserResource `json:"resources"`
|
||||
}
|
||||
|
||||
type Space struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Name string `json:"name"`
|
||||
OrganizationGuid string `json:"organization_guid"`
|
||||
OrgURL string `json:"organization_url"`
|
||||
OrgData OrgResource `json:"organization"`
|
||||
QuotaDefinitionGuid string `json:"space_quota_definition_guid"`
|
||||
IsolationSegmentGuid string `json:"isolation_segment_guid"`
|
||||
AllowSSH bool `json:"allow_ssh"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type SpaceSummary struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
Apps []AppSummary `json:"apps"`
|
||||
Services []ServiceSummary `json:"services"`
|
||||
}
|
||||
|
||||
type SpaceRoleResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []SpaceRoleResource `json:"resources"`
|
||||
}
|
||||
|
||||
type SpaceRoleResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity SpaceRole `json:"entity"`
|
||||
}
|
||||
|
||||
type SpaceRole struct {
|
||||
Guid string `json:"guid"`
|
||||
Admin bool `json:"admin"`
|
||||
Active bool `json:"active"`
|
||||
DefaultSpaceGuid string `json:"default_space_guid"`
|
||||
Username string `json:"username"`
|
||||
SpaceRoles []string `json:"space_roles"`
|
||||
SpacesUrl string `json:"spaces_url"`
|
||||
OrganizationsUrl string `json:"organizations_url"`
|
||||
ManagedOrganizationsUrl string `json:"managed_organizations_url"`
|
||||
BillingManagedOrganizationsUrl string `json:"billing_managed_organizations_url"`
|
||||
AuditedOrganizationsUrl string `json:"audited_organizations_url"`
|
||||
ManagedSpacesUrl string `json:"managed_spaces_url"`
|
||||
AuditedSpacesUrl string `json:"audited_spaces_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (s *Space) Org() (Org, error) {
|
||||
var orgResource OrgResource
|
||||
r := s.c.NewRequest("GET", s.OrgURL)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Org{}, errors.Wrap(err, "Error requesting org")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return Org{}, errors.Wrap(err, "Error reading org request")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &orgResource)
|
||||
if err != nil {
|
||||
return Org{}, errors.Wrap(err, "Error unmarshaling org")
|
||||
}
|
||||
return s.c.mergeOrgResource(orgResource), nil
|
||||
}
|
||||
|
||||
func (s *Space) Quota() (*SpaceQuota, error) {
|
||||
var spaceQuota *SpaceQuota
|
||||
var spaceQuotaResource SpaceQuotasResource
|
||||
if s.QuotaDefinitionGuid == "" {
|
||||
return nil, nil
|
||||
}
|
||||
requestUrl := fmt.Sprintf("/v2/space_quota_definitions/%s", s.QuotaDefinitionGuid)
|
||||
r := s.c.NewRequest("GET", requestUrl)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return &SpaceQuota{}, errors.Wrap(err, "Error requesting space quota")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return &SpaceQuota{}, errors.Wrap(err, "Error reading space quota body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &spaceQuotaResource)
|
||||
if err != nil {
|
||||
return &SpaceQuota{}, errors.Wrap(err, "Error unmarshalling space quota")
|
||||
}
|
||||
spaceQuota = &spaceQuotaResource.Entity
|
||||
spaceQuota.Guid = spaceQuotaResource.Meta.Guid
|
||||
spaceQuota.c = s.c
|
||||
return spaceQuota, nil
|
||||
}
|
||||
|
||||
func (s *Space) Summary() (SpaceSummary, error) {
|
||||
var spaceSummary SpaceSummary
|
||||
requestUrl := fmt.Sprintf("/v2/spaces/%s/summary", s.Guid)
|
||||
r := s.c.NewRequest("GET", requestUrl)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return SpaceSummary{}, errors.Wrap(err, "Error requesting space summary")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return SpaceSummary{}, errors.Wrap(err, "Error reading space summary body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &spaceSummary)
|
||||
if err != nil {
|
||||
return SpaceSummary{}, errors.Wrap(err, "Error unmarshalling space summary")
|
||||
}
|
||||
return spaceSummary, nil
|
||||
}
|
||||
|
||||
func (s *Space) Roles() ([]SpaceRole, error) {
|
||||
var roles []SpaceRole
|
||||
requestUrl := fmt.Sprintf("/v2/spaces/%s/user_roles", s.Guid)
|
||||
for {
|
||||
rolesResp, err := s.c.getSpaceRolesResponse(requestUrl)
|
||||
if err != nil {
|
||||
return roles, err
|
||||
}
|
||||
for _, role := range rolesResp.Resources {
|
||||
role.Entity.Guid = role.Meta.Guid
|
||||
role.Entity.c = s.c
|
||||
roles = append(roles, role.Entity)
|
||||
}
|
||||
requestUrl = rolesResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateSpace(req SpaceRequest) (Space, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/spaces", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Space{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleSpaceResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateSpace(spaceGUID string, req SpaceRequest) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.Update(req)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteSpace(guid string, recursive, async bool) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/spaces/%s?recursive=%t&async=%t", guid, recursive, async)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting space %s, response code: %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceManagersByQuery(spaceGUID string, query url.Values) ([]User, error) {
|
||||
return c.listSpaceUsersByRoleAndQuery(spaceGUID, "managers", query)
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceManagers(spaceGUID string) ([]User, error) {
|
||||
return c.ListSpaceManagersByQuery(spaceGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceAuditorsByQuery(spaceGUID string, query url.Values) ([]User, error) {
|
||||
return c.listSpaceUsersByRoleAndQuery(spaceGUID, "auditors", query)
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceAuditors(spaceGUID string) ([]User, error) {
|
||||
return c.ListSpaceAuditorsByQuery(spaceGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceDevelopersByQuery(spaceGUID string, query url.Values) ([]User, error) {
|
||||
return c.listSpaceUsersByRoleAndQuery(spaceGUID, "developers", query)
|
||||
}
|
||||
|
||||
func (c *Client) listSpaceUsersByRoleAndQuery(spaceGUID, role string, query url.Values) ([]User, error) {
|
||||
var users []User
|
||||
requestURL := fmt.Sprintf("/v2/spaces/%s/%s?%s", spaceGUID, role, query.Encode())
|
||||
for {
|
||||
userResp, err := c.getUserResponse(requestURL)
|
||||
if err != nil {
|
||||
return []User{}, err
|
||||
}
|
||||
for _, u := range userResp.Resources {
|
||||
users = append(users, c.mergeUserResource(u))
|
||||
}
|
||||
requestURL = userResp.NextUrl
|
||||
if requestURL == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceDevelopers(spaceGUID string) ([]User, error) {
|
||||
return c.ListSpaceDevelopersByQuery(spaceGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceDeveloper(spaceGUID, userGUID string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateDeveloper(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceDeveloperByUsername(spaceGUID, name string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateDeveloperByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceDeveloperByUsernameAndOrigin(spaceGUID, name, origin string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateDeveloperByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceDeveloper(spaceGUID, userGUID string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveDeveloper(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceDeveloperByUsername(spaceGUID, name string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveDeveloperByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceDeveloperByUsernameAndOrigin(spaceGUID, name, origin string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveDeveloperByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceAuditor(spaceGUID, userGUID string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateAuditor(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceAuditorByUsername(spaceGUID, name string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateAuditorByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceAuditorByUsernameAndOrigin(spaceGUID, name, origin string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateAuditorByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceAuditor(spaceGUID, userGUID string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveAuditor(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceAuditorByUsername(spaceGUID, name string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveAuditorByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceAuditorByUsernameAndOrigin(spaceGUID, name, origin string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveAuditorByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceManager(spaceGUID, userGUID string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateManager(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceManagerByUsername(spaceGUID, name string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateManagerByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) AssociateSpaceManagerByUsernameAndOrigin(spaceGUID, name, origin string) (Space, error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.AssociateManagerByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceManager(spaceGUID, userGUID string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveManager(userGUID)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceManagerByUsername(spaceGUID, name string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveManagerByUsername(name)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveSpaceManagerByUsernameAndOrigin(spaceGUID, name, origin string) error {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.RemoveManagerByUsernameAndOrigin(name, origin)
|
||||
}
|
||||
|
||||
func (s *Space) AssociateDeveloper(userGUID string) (Space, error) {
|
||||
return s.associateRole(userGUID, "developers")
|
||||
}
|
||||
|
||||
func (s *Space) AssociateDeveloperByUsername(name string) (Space, error) {
|
||||
return s.associateUserByRole(name, "developers", "")
|
||||
}
|
||||
|
||||
func (s *Space) AssociateDeveloperByUsernameAndOrigin(name, origin string) (Space, error) {
|
||||
return s.associateUserByRole(name, "developers", origin)
|
||||
}
|
||||
|
||||
func (s *Space) RemoveDeveloper(userGUID string) error {
|
||||
return s.removeRole(userGUID, "developers")
|
||||
}
|
||||
|
||||
func (s *Space) RemoveDeveloperByUsername(name string) error {
|
||||
return s.removeUserByRole(name, "developers", "")
|
||||
}
|
||||
|
||||
func (s *Space) RemoveDeveloperByUsernameAndOrigin(name, origin string) error {
|
||||
return s.removeUserByRole(name, "developers", origin)
|
||||
}
|
||||
|
||||
func (s *Space) AssociateAuditor(userGUID string) (Space, error) {
|
||||
return s.associateRole(userGUID, "auditors")
|
||||
}
|
||||
|
||||
func (s *Space) AssociateAuditorByUsername(name string) (Space, error) {
|
||||
return s.associateUserByRole(name, "auditors", "")
|
||||
}
|
||||
|
||||
func (s *Space) AssociateAuditorByUsernameAndOrigin(name, origin string) (Space, error) {
|
||||
return s.associateUserByRole(name, "auditors", origin)
|
||||
}
|
||||
|
||||
func (s *Space) RemoveAuditor(userGUID string) error {
|
||||
return s.removeRole(userGUID, "auditors")
|
||||
}
|
||||
|
||||
func (s *Space) RemoveAuditorByUsername(name string) error {
|
||||
return s.removeUserByRole(name, "auditors", "")
|
||||
}
|
||||
|
||||
func (s *Space) RemoveAuditorByUsernameAndOrigin(name, origin string) error {
|
||||
return s.removeUserByRole(name, "auditors", origin)
|
||||
}
|
||||
|
||||
func (s *Space) AssociateManager(userGUID string) (Space, error) {
|
||||
return s.associateRole(userGUID, "managers")
|
||||
}
|
||||
|
||||
func (s *Space) AssociateManagerByUsername(name string) (Space, error) {
|
||||
return s.associateUserByRole(name, "managers", "")
|
||||
}
|
||||
|
||||
func (s *Space) AssociateManagerByUsernameAndOrigin(name, origin string) (Space, error) {
|
||||
return s.associateUserByRole(name, "managers", origin)
|
||||
}
|
||||
|
||||
func (s *Space) RemoveManager(userGUID string) error {
|
||||
return s.removeRole(userGUID, "managers")
|
||||
}
|
||||
|
||||
func (s *Space) RemoveManagerByUsername(name string) error {
|
||||
return s.removeUserByRole(name, "managers", "")
|
||||
}
|
||||
func (s *Space) RemoveManagerByUsernameAndOrigin(name, origin string) error {
|
||||
return s.removeUserByRole(name, "managers", origin)
|
||||
}
|
||||
|
||||
func (s *Space) associateRole(userGUID, role string) (Space, error) {
|
||||
requestUrl := fmt.Sprintf("/v2/spaces/%s/%s/%s", s.Guid, role, userGUID)
|
||||
r := s.c.NewRequest("PUT", requestUrl)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Space{}, errors.Wrapf(err, "Error associating %s %s, response code: %d", role, userGUID, resp.StatusCode)
|
||||
}
|
||||
return s.c.handleSpaceResp(resp)
|
||||
}
|
||||
|
||||
func (s *Space) associateUserByRole(name, role, origin string) (Space, error) {
|
||||
requestUrl := fmt.Sprintf("/v2/spaces/%s/%s", s.Guid, role)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
payload := make(map[string]string)
|
||||
payload["username"] = name
|
||||
if origin != "" {
|
||||
payload["origin"] = origin
|
||||
}
|
||||
err := json.NewEncoder(buf).Encode(payload)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
r := s.c.NewRequestWithBody("PUT", requestUrl, buf)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Space{}, errors.Wrapf(err, "Error associating %s %s, response code: %d", role, name, resp.StatusCode)
|
||||
}
|
||||
return s.c.handleSpaceResp(resp)
|
||||
}
|
||||
|
||||
func (s *Space) removeRole(userGUID, role string) error {
|
||||
requestUrl := fmt.Sprintf("/v2/spaces/%s/%s/%s", s.Guid, role, userGUID)
|
||||
r := s.c.NewRequest("DELETE", requestUrl)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error removing %s %s, response code: %d", role, userGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Space) removeUserByRole(name, role, origin string) error {
|
||||
var requestURL string
|
||||
var method string
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
payload := make(map[string]string)
|
||||
payload["username"] = name
|
||||
if origin != "" {
|
||||
payload["origin"] = origin
|
||||
requestURL = fmt.Sprintf("/v2/spaces/%s/%s/remove", s.Guid, role)
|
||||
method = "POST"
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("/v2/spaces/%s/%s", s.Guid, role)
|
||||
method = "DELETE"
|
||||
}
|
||||
err := json.NewEncoder(buf).Encode(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := s.c.NewRequestWithBody(method, requestURL, buf)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.Wrapf(err, "Error removing %s %s, response code: %d", role, name, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaceSecGroups(spaceGUID string) (secGroups []SecGroup, err error) {
|
||||
space := Space{Guid: spaceGUID, c: c}
|
||||
return space.ListSecGroups()
|
||||
}
|
||||
|
||||
func (s *Space) ListSecGroups() (secGroups []SecGroup, err error) {
|
||||
requestURL := fmt.Sprintf("/v2/spaces/%s/security_groups?inline-relations-depth=1", s.Guid)
|
||||
for requestURL != "" {
|
||||
var secGroupResp SecGroupResponse
|
||||
r := s.c.NewRequest("GET", requestURL)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting sec groups")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading sec group response body")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &secGroupResp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling sec group")
|
||||
}
|
||||
|
||||
for _, secGroup := range secGroupResp.Resources {
|
||||
secGroup.Entity.Guid = secGroup.Meta.Guid
|
||||
secGroup.Entity.c = s.c
|
||||
for i, space := range secGroup.Entity.SpacesData {
|
||||
space.Entity.Guid = space.Meta.Guid
|
||||
secGroup.Entity.SpacesData[i] = space
|
||||
}
|
||||
if len(secGroup.Entity.SpacesData) == 0 {
|
||||
spaces, err := secGroup.Entity.ListSpaceResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, space := range spaces {
|
||||
secGroup.Entity.SpacesData = append(secGroup.Entity.SpacesData, space)
|
||||
}
|
||||
}
|
||||
secGroups = append(secGroups, secGroup.Entity)
|
||||
}
|
||||
|
||||
requestURL = secGroupResp.NextUrl
|
||||
resp.Body.Close()
|
||||
}
|
||||
return secGroups, nil
|
||||
}
|
||||
|
||||
func (s *Space) GetServiceOfferings() (ServiceOfferingResponse, error) {
|
||||
var response ServiceOfferingResponse
|
||||
requestURL := fmt.Sprintf("/v2/spaces/%s/services", s.Guid)
|
||||
req := s.c.NewRequest("GET", requestURL)
|
||||
|
||||
resp, err := s.c.DoRequest(req)
|
||||
if err != nil {
|
||||
return ServiceOfferingResponse{}, errors.Wrap(err, "Error requesting service offerings")
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ServiceOfferingResponse{}, errors.Wrap(err, "Error reading service offering response")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return ServiceOfferingResponse{}, errors.Wrap(err, "Error unmarshalling service offering response")
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Space) Update(req SpaceRequest) (Space, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
r := s.c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/spaces/%s", s.Guid), buf)
|
||||
resp, err := s.c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return Space{}, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return s.c.handleSpaceResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) ListSpacesByQuery(query url.Values) ([]Space, error) {
|
||||
return c.fetchSpaces("/v2/spaces?" + query.Encode())
|
||||
}
|
||||
|
||||
func (c *Client) ListSpaces() ([]Space, error) {
|
||||
return c.ListSpacesByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) fetchSpaces(requestUrl string) ([]Space, error) {
|
||||
var spaces []Space
|
||||
for {
|
||||
spaceResp, err := c.getSpaceResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []Space{}, err
|
||||
}
|
||||
for _, space := range spaceResp.Resources {
|
||||
spaces = append(spaces, c.mergeSpaceResource(space))
|
||||
}
|
||||
requestUrl = spaceResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return spaces, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSpaceByName(spaceName string, orgGuid string) (space Space, err error) {
|
||||
query := url.Values{}
|
||||
query.Add("q", fmt.Sprintf("organization_guid:%s", orgGuid))
|
||||
query.Add("q", fmt.Sprintf("name:%s", spaceName))
|
||||
spaces, err := c.ListSpacesByQuery(query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(spaces) == 0 {
|
||||
return space, fmt.Errorf("No space found with name: `%s` in org with GUID: `%s`", spaceName, orgGuid)
|
||||
}
|
||||
|
||||
return spaces[0], nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) GetSpaceByGuid(spaceGUID string) (Space, error) {
|
||||
requestUrl := fmt.Sprintf("/v2/spaces/%s", spaceGUID)
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return Space{}, errors.Wrap(err, "Error requesting space info")
|
||||
}
|
||||
return c.handleSpaceResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) getSpaceResponse(requestUrl string) (SpaceResponse, error) {
|
||||
var spaceResp SpaceResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return SpaceResponse{}, errors.Wrap(err, "Error requesting spaces")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return SpaceResponse{}, errors.Wrap(err, "Error reading space request")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &spaceResp)
|
||||
if err != nil {
|
||||
return SpaceResponse{}, errors.Wrap(err, "Error unmarshalling space")
|
||||
}
|
||||
return spaceResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) getSpaceRolesResponse(requestUrl string) (SpaceRoleResponse, error) {
|
||||
var roleResp SpaceRoleResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return roleResp, errors.Wrap(err, "Error requesting space roles")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return roleResp, errors.Wrap(err, "Error reading space roles request")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &roleResp)
|
||||
if err != nil {
|
||||
return roleResp, errors.Wrap(err, "Error unmarshalling space roles")
|
||||
}
|
||||
return roleResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) handleSpaceResp(resp *http.Response) (Space, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
var spaceResource SpaceResource
|
||||
err = json.Unmarshal(body, &spaceResource)
|
||||
if err != nil {
|
||||
return Space{}, err
|
||||
}
|
||||
return c.mergeSpaceResource(spaceResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeSpaceResource(space SpaceResource) Space {
|
||||
space.Entity.Guid = space.Meta.Guid
|
||||
space.Entity.CreatedAt = space.Meta.CreatedAt
|
||||
space.Entity.UpdatedAt = space.Meta.UpdatedAt
|
||||
space.Entity.c = c
|
||||
return space.Entity
|
||||
}
|
||||
|
||||
type serviceOfferingExtra ServiceOfferingExtra
|
||||
|
||||
func (resource *ServiceOfferingExtra) UnmarshalJSON(rawData []byte) error {
|
||||
if string(rawData) == "null" {
|
||||
return nil
|
||||
}
|
||||
|
||||
extra := serviceOfferingExtra{}
|
||||
|
||||
unquoted, err := strconv.Unquote(string(rawData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(unquoted), &extra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*resource = ServiceOfferingExtra(extra)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) IsolationSegmentForSpace(spaceGUID, isolationSegmentGUID string) error {
|
||||
return c.updateSpaceIsolationSegment(spaceGUID, map[string]interface{}{"guid": isolationSegmentGUID})
|
||||
}
|
||||
|
||||
func (c *Client) ResetIsolationSegmentForSpace(spaceGUID string) error {
|
||||
return c.updateSpaceIsolationSegment(spaceGUID, nil)
|
||||
}
|
||||
|
||||
func (c *Client) updateSpaceIsolationSegment(spaceGUID string, data interface{}) error {
|
||||
requestURL := fmt.Sprintf("/v3/spaces/%s/relationships/isolation_segment", spaceGUID)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(map[string]interface{}{"data": data})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := c.NewRequestWithBody("PATCH", requestURL, buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.Wrapf(err, "Error setting isolation segment for space %s, response code: %d", spaceGUID, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type StacksResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []StacksResource `json:"resources"`
|
||||
}
|
||||
|
||||
type StacksResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity Stack `json:"entity"`
|
||||
}
|
||||
|
||||
type Stack struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Description string `json:"description"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (c *Client) ListStacksByQuery(query url.Values) ([]Stack, error) {
|
||||
var stacks []Stack
|
||||
requestUrl := "/v2/stacks?" + query.Encode()
|
||||
for {
|
||||
stacksResp, err := c.getStacksResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []Stack{}, err
|
||||
}
|
||||
for _, stack := range stacksResp.Resources {
|
||||
stack.Entity.Guid = stack.Meta.Guid
|
||||
stack.Entity.CreatedAt = stack.Meta.CreatedAt
|
||||
stack.Entity.UpdatedAt = stack.Meta.UpdatedAt
|
||||
stack.Entity.c = c
|
||||
stacks = append(stacks, stack.Entity)
|
||||
}
|
||||
requestUrl = stacksResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return stacks, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListStacks() ([]Stack, error) {
|
||||
return c.ListStacksByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) getStacksResponse(requestUrl string) (StacksResponse, error) {
|
||||
var stacksResp StacksResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return StacksResponse{}, errors.Wrap(err, "Error requesting stacks")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return StacksResponse{}, errors.Wrap(err, "Error reading stacks body")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &stacksResp)
|
||||
if err != nil {
|
||||
return StacksResponse{}, errors.Wrap(err, "Error unmarshalling stacks")
|
||||
}
|
||||
return stacksResp, nil
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TaskListResponse is the JSON response from the API.
|
||||
type TaskListResponse struct {
|
||||
Pagination Pagination `json:"pagination"`
|
||||
Tasks []Task `json:"resources"`
|
||||
}
|
||||
|
||||
// Task is a description of a task element.
|
||||
type Task struct {
|
||||
GUID string `json:"guid"`
|
||||
SequenceID int `json:"sequence_id"`
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
State string `json:"state"`
|
||||
MemoryInMb int `json:"memory_in_mb"`
|
||||
DiskInMb int `json:"disk_in_mb"`
|
||||
Result struct {
|
||||
FailureReason string `json:"failure_reason"`
|
||||
} `json:"result"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DropletGUID string `json:"droplet_guid"`
|
||||
Links struct {
|
||||
Self Link `json:"self"`
|
||||
App Link `json:"app"`
|
||||
Droplet Link `json:"droplet"`
|
||||
} `json:"links"`
|
||||
}
|
||||
|
||||
// TaskRequest is a v3 JSON object as described in:
|
||||
// http://v3-apidocs.cloudfoundry.org/version/3.0.0/index.html#create-a-task
|
||||
type TaskRequest struct {
|
||||
Command string `json:"command"`
|
||||
Name string `json:"name"`
|
||||
MemoryInMegabyte int `json:"memory_in_mb"`
|
||||
DiskInMegabyte int `json:"disk_in_mb"`
|
||||
DropletGUID string `json:"droplet_guid"`
|
||||
}
|
||||
|
||||
func (c *Client) makeTaskListRequestWithParams(baseUrl string, query url.Values) ([]byte, error) {
|
||||
requestUrl := baseUrl + "?" + query.Encode()
|
||||
req := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting tasks")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.Wrapf(err, "Error requesting tasks: status code not 200, it was %d", resp.StatusCode)
|
||||
}
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func parseTaskListRespones(answer []byte) (TaskListResponse, error) {
|
||||
var response TaskListResponse
|
||||
err := json.Unmarshal(answer, &response)
|
||||
if err != nil {
|
||||
return response, errors.Wrap(err, "Error unmarshaling response %v")
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) handleTasksApiCall(apiUrl string, query url.Values) ([]Task, error) {
|
||||
body, err := c.makeTaskListRequestWithParams(apiUrl, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting tasks")
|
||||
}
|
||||
response, err := parseTaskListRespones(body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading tasks")
|
||||
}
|
||||
return response.Tasks, nil
|
||||
}
|
||||
|
||||
// ListTasks returns all tasks the user has access to.
|
||||
// See http://v3-apidocs.cloudfoundry.org/version/3.12.0/index.html#list-tasks
|
||||
func (c *Client) ListTasks() ([]Task, error) {
|
||||
return c.handleTasksApiCall("/v3/tasks", url.Values{})
|
||||
}
|
||||
|
||||
// ListTasksByQuery returns all tasks the user has access to, with query parameters.
|
||||
// See http://v3-apidocs.cloudfoundry.org/version/3.12.0/index.html#list-tasks
|
||||
func (c *Client) ListTasksByQuery(query url.Values) ([]Task, error) {
|
||||
return c.handleTasksApiCall("/v3/tasks", query)
|
||||
}
|
||||
|
||||
// TasksByApp returns task structures which aligned to an app identified by the given guid.
|
||||
// See: http://v3-apidocs.cloudfoundry.org/version/3.12.0/index.html#list-tasks-for-an-app
|
||||
func (c *Client) TasksByApp(guid string) ([]Task, error) {
|
||||
return c.TasksByAppByQuery(guid, url.Values{})
|
||||
}
|
||||
|
||||
// TasksByAppByQuery returns task structures which aligned to an app identified by the given guid
|
||||
// and filtered by the given query parameters.
|
||||
// See: http://v3-apidocs.cloudfoundry.org/version/3.12.0/index.html#list-tasks-for-an-app
|
||||
func (c *Client) TasksByAppByQuery(guid string, query url.Values) ([]Task, error) {
|
||||
uri := fmt.Sprintf("/v3/apps/%s/tasks", guid)
|
||||
return c.handleTasksApiCall(uri, query)
|
||||
}
|
||||
|
||||
func createReader(tr TaskRequest) (io.Reader, error) {
|
||||
rmap := make(map[string]string)
|
||||
rmap["command"] = tr.Command
|
||||
if tr.Name != "" {
|
||||
rmap["name"] = tr.Name
|
||||
}
|
||||
// setting droplet GUID causing issues
|
||||
if tr.MemoryInMegabyte != 0 {
|
||||
rmap["memory_in_mb"] = fmt.Sprintf("%d", tr.MemoryInMegabyte)
|
||||
}
|
||||
if tr.DiskInMegabyte != 0 {
|
||||
rmap["disk_in_mb"] = fmt.Sprintf("%d", tr.DiskInMegabyte)
|
||||
}
|
||||
|
||||
bodyReader := bytes.NewBuffer(nil)
|
||||
enc := json.NewEncoder(bodyReader)
|
||||
if err := enc.Encode(rmap); err != nil {
|
||||
return nil, errors.Wrap(err, "Error during encoding task request")
|
||||
}
|
||||
return bodyReader, nil
|
||||
}
|
||||
|
||||
// CreateTask creates a new task in CF system and returns its structure.
|
||||
func (c *Client) CreateTask(tr TaskRequest) (task Task, err error) {
|
||||
bodyReader, err := createReader(tr)
|
||||
if err != nil {
|
||||
return task, err
|
||||
}
|
||||
|
||||
request := fmt.Sprintf("/v3/apps/%s/tasks", tr.DropletGUID)
|
||||
req := c.NewRequestWithBody("POST", request, bodyReader)
|
||||
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return task, errors.Wrap(err, "Error creating task")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return task, errors.Wrap(err, "Error reading task after creation")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &task)
|
||||
if err != nil {
|
||||
return task, errors.Wrap(err, "Error unmarshaling task")
|
||||
}
|
||||
return task, err
|
||||
}
|
||||
|
||||
// GetTaskByGuid returns a task structure by requesting it with the tasks GUID.
|
||||
func (c *Client) GetTaskByGuid(guid string) (task Task, err error) {
|
||||
request := fmt.Sprintf("/v3/tasks/%s", guid)
|
||||
req := c.NewRequest("GET", request)
|
||||
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return task, errors.Wrap(err, "Error requesting task")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return task, errors.Wrap(err, "Error reading task")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &task)
|
||||
if err != nil {
|
||||
return task, errors.Wrap(err, "Error unmarshaling task")
|
||||
}
|
||||
return task, err
|
||||
}
|
||||
|
||||
func (c *Client) TaskByGuid(guid string) (task Task, err error) {
|
||||
return c.GetTaskByGuid(guid)
|
||||
}
|
||||
|
||||
// TerminateTask cancels a task identified by its GUID.
|
||||
func (c *Client) TerminateTask(guid string) error {
|
||||
req := c.NewRequest("PUT", fmt.Sprintf("/v3/tasks/%s/cancel", guid))
|
||||
resp, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error terminating task")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 202 {
|
||||
return errors.Wrapf(err, "Failed terminating task, response status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package cfclient
|
||||
|
||||
type Meta struct {
|
||||
Guid string `json:"guid"`
|
||||
Url string `json:"url"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
185
vendor/github.com/cloudfoundry-community/go-cfclient/user_provided_service_instances.go
generated
vendored
Normal file
185
vendor/github.com/cloudfoundry-community/go-cfclient/user_provided_service_instances.go
generated
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UserProvidedServiceInstancesResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []UserProvidedServiceInstanceResource `json:"resources"`
|
||||
}
|
||||
|
||||
type UserProvidedServiceInstanceResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity UserProvidedServiceInstance `json:"entity"`
|
||||
}
|
||||
|
||||
type UserProvidedServiceInstance struct {
|
||||
Guid string `json:"guid"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Credentials map[string]interface{} `json:"credentials"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
Type string `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
SpaceUrl string `json:"space_url"`
|
||||
ServiceBindingsUrl string `json:"service_bindings_url"`
|
||||
RoutesUrl string `json:"routes_url"`
|
||||
RouteServiceUrl string `json:"route_service_url"`
|
||||
SyslogDrainUrl string `json:"syslog_drain_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type UserProvidedServiceInstanceRequest struct {
|
||||
Name string `json:"name"`
|
||||
Credentials map[string]interface{} `json:"credentials"`
|
||||
SpaceGuid string `json:"space_guid"`
|
||||
Tags []string `json:"tags"`
|
||||
RouteServiceUrl string `json:"route_service_url"`
|
||||
SyslogDrainUrl string `json:"syslog_drain_url"`
|
||||
}
|
||||
|
||||
func (c *Client) ListUserProvidedServiceInstancesByQuery(query url.Values) ([]UserProvidedServiceInstance, error) {
|
||||
var instances []UserProvidedServiceInstance
|
||||
|
||||
requestUrl := "/v2/user_provided_service_instances?" + query.Encode()
|
||||
for {
|
||||
var sir UserProvidedServiceInstancesResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error requesting user provided service instances")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading user provided service instances request:")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &sir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error unmarshaling user provided service instances")
|
||||
}
|
||||
for _, instance := range sir.Resources {
|
||||
instance.Entity.Guid = instance.Meta.Guid
|
||||
instance.Entity.CreatedAt = instance.Meta.CreatedAt
|
||||
instance.Entity.UpdatedAt = instance.Meta.UpdatedAt
|
||||
instance.Entity.c = c
|
||||
instances = append(instances, instance.Entity)
|
||||
}
|
||||
|
||||
requestUrl = sir.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListUserProvidedServiceInstances() ([]UserProvidedServiceInstance, error) {
|
||||
return c.ListUserProvidedServiceInstancesByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) GetUserProvidedServiceInstanceByGuid(guid string) (UserProvidedServiceInstance, error) {
|
||||
var sir UserProvidedServiceInstanceResource
|
||||
req := c.NewRequest("GET", "/v2/user_provided_service_instances/"+guid)
|
||||
res, err := c.DoRequest(req)
|
||||
if err != nil {
|
||||
return UserProvidedServiceInstance{}, errors.Wrap(err, "Error requesting user provided service instance")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return UserProvidedServiceInstance{}, errors.Wrap(err, "Error reading user provided service instance response")
|
||||
}
|
||||
err = json.Unmarshal(data, &sir)
|
||||
if err != nil {
|
||||
return UserProvidedServiceInstance{}, errors.Wrap(err, "Error JSON parsing user provided service instance response")
|
||||
}
|
||||
sir.Entity.Guid = sir.Meta.Guid
|
||||
sir.Entity.CreatedAt = sir.Meta.CreatedAt
|
||||
sir.Entity.UpdatedAt = sir.Meta.UpdatedAt
|
||||
sir.Entity.c = c
|
||||
return sir.Entity, nil
|
||||
}
|
||||
|
||||
func (c *Client) UserProvidedServiceInstanceByGuid(guid string) (UserProvidedServiceInstance, error) {
|
||||
return c.GetUserProvidedServiceInstanceByGuid(guid)
|
||||
}
|
||||
|
||||
func (c *Client) CreateUserProvidedServiceInstance(req UserProvidedServiceInstanceRequest) (*UserProvidedServiceInstance, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/user_provided_service_instances", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return c.handleUserProvidedServiceInstanceResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteUserProvidedServiceInstance(guid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/user_provided_service_instances/%s", guid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting user provided service instance %s, response code %d", guid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) UpdateUserProvidedServiceInstance(guid string, req UserProvidedServiceInstanceRequest) (*UserProvidedServiceInstance, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := c.NewRequestWithBody("PUT", fmt.Sprintf("/v2/user_provided_service_instances/%s", guid), buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("CF API returned with status code %d", resp.StatusCode)
|
||||
}
|
||||
return c.handleUserProvidedServiceInstanceResp(resp)
|
||||
}
|
||||
|
||||
func (c *Client) handleUserProvidedServiceInstanceResp(resp *http.Response) (*UserProvidedServiceInstance, error) {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var upsResource UserProvidedServiceInstanceResource
|
||||
err = json.Unmarshal(body, &upsResource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.mergeUserProvidedServiceInstanceResource(upsResource), nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeUserProvidedServiceInstanceResource(ups UserProvidedServiceInstanceResource) *UserProvidedServiceInstance {
|
||||
ups.Entity.Guid = ups.Meta.Guid
|
||||
ups.Entity.CreatedAt = ups.Meta.CreatedAt
|
||||
ups.Entity.UpdatedAt = ups.Meta.UpdatedAt
|
||||
ups.Entity.c = c
|
||||
return &ups.Entity
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package cfclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UserRequest struct {
|
||||
Guid string `json:"guid"`
|
||||
DefaultSpaceGuid string `json:"default_space_guid,omitempty"`
|
||||
}
|
||||
|
||||
type Users []User
|
||||
|
||||
type User struct {
|
||||
Guid string `json:"guid"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Admin bool `json:"admin"`
|
||||
Active bool `json:"active"`
|
||||
DefaultSpaceGUID string `json:"default_space_guid"`
|
||||
Username string `json:"username"`
|
||||
SpacesURL string `json:"spaces_url"`
|
||||
OrgsURL string `json:"organizations_url"`
|
||||
ManagedOrgsURL string `json:"managed_organizations_url"`
|
||||
BillingManagedOrgsURL string `json:"billing_managed_organizations_url"`
|
||||
AuditedOrgsURL string `json:"audited_organizations_url"`
|
||||
ManagedSpacesURL string `json:"managed_spaces_url"`
|
||||
AuditedSpacesURL string `json:"audited_spaces_url"`
|
||||
c *Client
|
||||
}
|
||||
|
||||
type UserResource struct {
|
||||
Meta Meta `json:"metadata"`
|
||||
Entity User `json:"entity"`
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
Count int `json:"total_results"`
|
||||
Pages int `json:"total_pages"`
|
||||
NextUrl string `json:"next_url"`
|
||||
Resources []UserResource `json:"resources"`
|
||||
}
|
||||
|
||||
// GetUserByGUID retrieves the user with the provided guid.
|
||||
func (c *Client) GetUserByGUID(guid string) (User, error) {
|
||||
var userRes UserResource
|
||||
r := c.NewRequest("GET", "/v2/users/"+guid)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
err = json.Unmarshal(body, &userRes)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
return c.mergeUserResource(userRes), nil
|
||||
}
|
||||
|
||||
func (c *Client) ListUsersByQuery(query url.Values) (Users, error) {
|
||||
var users []User
|
||||
requestUrl := "/v2/users?" + query.Encode()
|
||||
for {
|
||||
userResp, err := c.getUserResponse(requestUrl)
|
||||
if err != nil {
|
||||
return []User{}, err
|
||||
}
|
||||
for _, user := range userResp.Resources {
|
||||
user.Entity.Guid = user.Meta.Guid
|
||||
user.Entity.CreatedAt = user.Meta.CreatedAt
|
||||
user.Entity.UpdatedAt = user.Meta.UpdatedAt
|
||||
user.Entity.c = c
|
||||
users = append(users, user.Entity)
|
||||
}
|
||||
requestUrl = userResp.NextUrl
|
||||
if requestUrl == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListUsers() (Users, error) {
|
||||
return c.ListUsersByQuery(nil)
|
||||
}
|
||||
|
||||
func (c *Client) ListUserSpaces(userGuid string) ([]Space, error) {
|
||||
return c.fetchSpaces(fmt.Sprintf("/v2/users/%s/spaces", userGuid))
|
||||
}
|
||||
|
||||
func (c *Client) ListUserAuditedSpaces(userGuid string) ([]Space, error) {
|
||||
return c.fetchSpaces(fmt.Sprintf("/v2/users/%s/audited_spaces", userGuid))
|
||||
}
|
||||
|
||||
func (c *Client) ListUserManagedSpaces(userGuid string) ([]Space, error) {
|
||||
return c.fetchSpaces(fmt.Sprintf("/v2/users/%s/managed_spaces", userGuid))
|
||||
}
|
||||
|
||||
func (c *Client) ListUserOrgs(userGuid string) ([]Org, error) {
|
||||
return c.fetchOrgs(fmt.Sprintf("/v2/users/%s/organizations", userGuid))
|
||||
}
|
||||
|
||||
func (c *Client) ListUserManagedOrgs(userGuid string) ([]Org, error) {
|
||||
return c.fetchOrgs(fmt.Sprintf("/v2/users/%s/managed_organizations", userGuid))
|
||||
}
|
||||
|
||||
func (c *Client) ListUserAuditedOrgs(userGuid string) ([]Org, error) {
|
||||
return c.fetchOrgs(fmt.Sprintf("/v2/users/%s/audited_organizations", userGuid))
|
||||
}
|
||||
|
||||
func (c *Client) ListUserBillingManagedOrgs(userGuid string) ([]Org, error) {
|
||||
return c.fetchOrgs(fmt.Sprintf("/v2/users/%s/billing_managed_organizations", userGuid))
|
||||
}
|
||||
|
||||
func (c *Client) CreateUser(req UserRequest) (User, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := json.NewEncoder(buf).Encode(req)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
r := c.NewRequestWithBody("POST", "/v2/users", buf)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return User{}, errors.Wrapf(err, "Error creating user, response code: %d", resp.StatusCode)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
var userResource UserResource
|
||||
err = json.Unmarshal(body, &userResource)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
user := userResource.Entity
|
||||
user.Guid = userResource.Meta.Guid
|
||||
user.c = c
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteUser(userGuid string) error {
|
||||
resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/users/%s", userGuid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return errors.Wrapf(err, "Error deleting user %s, response code: %d", userGuid, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u Users) GetUserByUsername(username string) User {
|
||||
for _, user := range u {
|
||||
if user.Username == username {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return User{}
|
||||
}
|
||||
|
||||
func (c *Client) getUserResponse(requestUrl string) (UserResponse, error) {
|
||||
var userResp UserResponse
|
||||
r := c.NewRequest("GET", requestUrl)
|
||||
resp, err := c.DoRequest(r)
|
||||
if err != nil {
|
||||
return UserResponse{}, errors.Wrap(err, "Error requesting users")
|
||||
}
|
||||
resBody, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return UserResponse{}, errors.Wrap(err, "Error reading user request")
|
||||
}
|
||||
err = json.Unmarshal(resBody, &userResp)
|
||||
if err != nil {
|
||||
return UserResponse{}, errors.Wrap(err, "Error unmarshalling user")
|
||||
}
|
||||
return userResp, nil
|
||||
}
|
||||
|
||||
func (c *Client) mergeUserResource(u UserResource) User {
|
||||
u.Entity.Guid = u.Meta.Guid
|
||||
u.Entity.CreatedAt = u.Meta.CreatedAt
|
||||
u.Entity.UpdatedAt = u.Meta.UpdatedAt
|
||||
u.Entity.c = c
|
||||
return u.Entity
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cfclient
|
||||
|
||||
// Pagination is used by the V3 apis
|
||||
type Pagination struct {
|
||||
TotalResults int `json:"total_results"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
First Link `json:"first"`
|
||||
Last Link `json:"last"`
|
||||
Next interface{} `json:"next"`
|
||||
Previous interface{} `json:"previous"`
|
||||
}
|
||||
|
||||
// Link is a HATEOAS-style link for v3 apis
|
||||
type Link struct {
|
||||
Href string `json:"href"`
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package hclog
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// WithContext inserts a logger into the context and is retrievable
|
||||
// with FromContext. The optional args can be set with the same syntax as
|
||||
// Logger.With to set fields on the inserted logger. This will not modify
|
||||
// the logger argument in-place.
|
||||
func WithContext(ctx context.Context, logger Logger, args ...interface{}) context.Context {
|
||||
// While we could call logger.With even with zero args, we have this
|
||||
// check to avoid unnecessary allocations around creating a copy of a
|
||||
// logger.
|
||||
if len(args) > 0 {
|
||||
logger = logger.With(args...)
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, contextKey, logger)
|
||||
}
|
||||
|
||||
// FromContext returns a logger from the context. This will return L()
|
||||
// (the default logger) if no logger is found in the context. Therefore,
|
||||
// this will never return a nil value.
|
||||
func FromContext(ctx context.Context) Logger {
|
||||
logger, _ := ctx.Value(contextKey).(Logger)
|
||||
if logger == nil {
|
||||
return L()
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// Unexported new type so that our context key never collides with another.
|
||||
type contextKeyType struct{}
|
||||
|
||||
// contextKey is the key used for the context to store the logger.
|
||||
var contextKey = contextKeyType{}
|
|
@ -22,7 +22,11 @@ var (
|
|||
// to be used in more specific contexts.
|
||||
func Default() Logger {
|
||||
protect.Do(func() {
|
||||
def = New(DefaultOptions)
|
||||
// If SetDefault was used before Default() was called, we need to
|
||||
// detect that here.
|
||||
if def == nil {
|
||||
def = New(DefaultOptions)
|
||||
}
|
||||
})
|
||||
|
||||
return def
|
||||
|
@ -32,3 +36,13 @@ func Default() Logger {
|
|||
func L() Logger {
|
||||
return Default()
|
||||
}
|
||||
|
||||
// SetDefault changes the logger to be returned by Default()and L()
|
||||
// to the one given. This allows packages to use the default logger
|
||||
// and have higher level packages change it to match the execution
|
||||
// environment. It returns any old default if there is one.
|
||||
func SetDefault(log Logger) Logger {
|
||||
old := def
|
||||
def = log
|
||||
return old
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ import (
|
|||
// contains millisecond precision
|
||||
const TimeFormat = "2006-01-02T15:04:05.000Z0700"
|
||||
|
||||
// errJsonUnsupportedTypeMsg is included in log json entries, if an arg cannot be serialized to json
|
||||
const errJsonUnsupportedTypeMsg = "logging contained values that don't serialize to json"
|
||||
|
||||
var (
|
||||
_levelToBracket = map[Level]string{
|
||||
Debug: "[DEBUG]",
|
||||
|
@ -296,39 +299,7 @@ func (l *intLogger) renderSlice(v reflect.Value) string {
|
|||
|
||||
// JSON logging function
|
||||
func (l *intLogger) logJSON(t time.Time, level Level, msg string, args ...interface{}) {
|
||||
vals := map[string]interface{}{
|
||||
"@message": msg,
|
||||
"@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"),
|
||||
}
|
||||
|
||||
var levelStr string
|
||||
switch level {
|
||||
case Error:
|
||||
levelStr = "error"
|
||||
case Warn:
|
||||
levelStr = "warn"
|
||||
case Info:
|
||||
levelStr = "info"
|
||||
case Debug:
|
||||
levelStr = "debug"
|
||||
case Trace:
|
||||
levelStr = "trace"
|
||||
default:
|
||||
levelStr = "all"
|
||||
}
|
||||
|
||||
vals["@level"] = levelStr
|
||||
|
||||
if l.name != "" {
|
||||
vals["@module"] = l.name
|
||||
}
|
||||
|
||||
if l.caller {
|
||||
if _, file, line, ok := runtime.Caller(3); ok {
|
||||
vals["@caller"] = fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
}
|
||||
|
||||
vals := l.jsonMapEntry(t, level, msg)
|
||||
args = append(l.implied, args...)
|
||||
|
||||
if args != nil && len(args) > 0 {
|
||||
|
@ -369,10 +340,51 @@ func (l *intLogger) logJSON(t time.Time, level Level, msg string, args ...interf
|
|||
|
||||
err := json.NewEncoder(l.writer).Encode(vals)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if _, ok := err.(*json.UnsupportedTypeError); ok {
|
||||
plainVal := l.jsonMapEntry(t, level, msg)
|
||||
plainVal["@warn"] = errJsonUnsupportedTypeMsg
|
||||
|
||||
json.NewEncoder(l.writer).Encode(plainVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l intLogger) jsonMapEntry(t time.Time, level Level, msg string) map[string]interface{} {
|
||||
vals := map[string]interface{}{
|
||||
"@message": msg,
|
||||
"@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"),
|
||||
}
|
||||
|
||||
var levelStr string
|
||||
switch level {
|
||||
case Error:
|
||||
levelStr = "error"
|
||||
case Warn:
|
||||
levelStr = "warn"
|
||||
case Info:
|
||||
levelStr = "info"
|
||||
case Debug:
|
||||
levelStr = "debug"
|
||||
case Trace:
|
||||
levelStr = "trace"
|
||||
default:
|
||||
levelStr = "all"
|
||||
}
|
||||
|
||||
vals["@level"] = levelStr
|
||||
|
||||
if l.name != "" {
|
||||
vals["@module"] = l.name
|
||||
}
|
||||
|
||||
if l.caller {
|
||||
if _, file, line, ok := runtime.Caller(4); ok {
|
||||
vals["@caller"] = fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// Emit the message and args at DEBUG level
|
||||
func (l *intLogger) Debug(msg string, args ...interface{}) {
|
||||
l.Log(Debug, msg, args...)
|
||||
|
@ -507,5 +519,9 @@ func (l *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
|
|||
}
|
||||
|
||||
func (l *intLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer {
|
||||
return &stdlogAdapter{l, opts.InferLevels}
|
||||
return &stdlogAdapter{
|
||||
log: l,
|
||||
inferLevels: opts.InferLevels,
|
||||
forceLevel: opts.ForceLevel,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,6 +143,12 @@ type StandardLoggerOptions struct {
|
|||
// This supports the strings like [ERROR], [ERR] [TRACE], [WARN], [INFO],
|
||||
// [DEBUG] and strip it off before reapplying it.
|
||||
InferLevels bool
|
||||
|
||||
// ForceLevel is used to force all output from the standard logger to be at
|
||||
// the specified level. Similar to InferLevels, this will strip any level
|
||||
// prefix contained in the logged string before applying the forced level.
|
||||
// If set, this override InferLevels.
|
||||
ForceLevel Level
|
||||
}
|
||||
|
||||
// LoggerOptions can be used to configure a new logger.
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
type stdlogAdapter struct {
|
||||
log Logger
|
||||
inferLevels bool
|
||||
forceLevel Level
|
||||
}
|
||||
|
||||
// Take the data, infer the levels if configured, and send it through
|
||||
|
@ -18,7 +19,27 @@ type stdlogAdapter struct {
|
|||
func (s *stdlogAdapter) Write(data []byte) (int, error) {
|
||||
str := string(bytes.TrimRight(data, " \t\n"))
|
||||
|
||||
if s.inferLevels {
|
||||
if s.forceLevel != NoLevel {
|
||||
// Use pickLevel to strip log levels included in the line since we are
|
||||
// forcing the level
|
||||
_, str := s.pickLevel(str)
|
||||
|
||||
// Log at the forced level
|
||||
switch s.forceLevel {
|
||||
case Trace:
|
||||
s.log.Trace(str)
|
||||
case Debug:
|
||||
s.log.Debug(str)
|
||||
case Info:
|
||||
s.log.Info(str)
|
||||
case Warn:
|
||||
s.log.Warn(str)
|
||||
case Error:
|
||||
s.log.Error(str)
|
||||
default:
|
||||
s.log.Info(str)
|
||||
}
|
||||
} else if s.inferLevels {
|
||||
level, str := s.pickLevel(str)
|
||||
switch level {
|
||||
case Trace:
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Jetbrains
|
||||
.idea*
|
||||
|
||||
# Binaries
|
||||
cmd/vault-plugin-auth-pcf/vault-plugin-auth-pcf
|
||||
cmd/verify/verify
|
||||
|
||||
pkg*
|
||||
bin*
|
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
|
@ -0,0 +1,57 @@
|
|||
TOOL?=vault-plugin-auth-pcf
|
||||
TEST?=$$(go list ./... | grep -v /vendor/ | grep -v teamcity)
|
||||
VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr
|
||||
EXTERNAL_TOOLS=\
|
||||
github.com/mitchellh/gox
|
||||
BUILD_TAGS?=${TOOL}
|
||||
GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor)
|
||||
|
||||
# bin generates the releaseable binaries for this plugin
|
||||
bin: fmtcheck generate
|
||||
@CGO_ENABLED=0 BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/build.sh'"
|
||||
|
||||
default: dev
|
||||
|
||||
# dev creates binaries for testing Vault locally. These are put
|
||||
# into ./bin/ as well as $GOPATH/bin.
|
||||
dev: fmtcheck generate
|
||||
@CGO_ENABLED=0 BUILD_TAGS='$(BUILD_TAGS)' VAULT_DEV_BUILD=1 sh -c "'$(CURDIR)/scripts/build.sh'"
|
||||
|
||||
# testshort runs the quick unit tests and vets the code
|
||||
testshort: fmtcheck generate
|
||||
CGO_ENABLED=0 VAULT_TOKEN= VAULT_ACC= go test -short -tags='$(BUILD_TAGS)' $(TEST) $(TESTARGS) -count=1 -timeout=20m -parallel=4
|
||||
|
||||
# test runs the unit tests and vets the code
|
||||
test: fmtcheck generate
|
||||
CGO_ENABLED=0 VAULT_TOKEN= VAULT_ACC= go test ./... -v -tags='$(BUILD_TAGS)' $(TEST) $(TESTARGS) -count=1 -timeout=20m -parallel=4
|
||||
|
||||
testcompile: fmtcheck generate
|
||||
@for pkg in $(TEST) ; do \
|
||||
go test -v -c -tags='$(BUILD_TAGS)' $$pkg -parallel=4 ; \
|
||||
done
|
||||
|
||||
# generate runs `go generate` to build the dynamically generated
|
||||
# source files.
|
||||
generate:
|
||||
go generate $(go list ./... | grep -v /vendor/)
|
||||
|
||||
# bootstrap the build by downloading additional tools
|
||||
bootstrap:
|
||||
@for tool in $(EXTERNAL_TOOLS) ; do \
|
||||
echo "Installing/Updating $$tool" ; \
|
||||
go get -u $$tool; \
|
||||
done
|
||||
|
||||
fmtcheck:
|
||||
@sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
|
||||
|
||||
fmt:
|
||||
gofmt -w $(GOFMT_FILES)
|
||||
|
||||
proto:
|
||||
protoc *.proto --go_out=plugins=grpc:.
|
||||
|
||||
tools:
|
||||
go install ./...
|
||||
|
||||
.PHONY: bin default generate test vet bootstrap fmt fmtcheck
|
|
@ -0,0 +1,483 @@
|
|||
# vault-plugin-auth-pcf
|
||||
|
||||
This plugin leverages PCF's [App and Container Identity Assurance](https://content.pivotal.io/blog/new-in-pcf-2-1-app-container-identity-assurance-via-automatic-cert-rotation)
|
||||
for authenticating to Vault.
|
||||
|
||||
## Known Risks
|
||||
|
||||
This authentication engine uses PCF's instance identity service to authenticate users to Vault. Because PCF
|
||||
makes its CA certificate and **private key** available to certain users at any time, it's possible for someone
|
||||
with access to them to self-issue identity certificates that meet the criteria for a Vault role, allowing
|
||||
them to gain unintended access to Vault.
|
||||
|
||||
For this reason, we recommend that if you choose this auth method, you **carefully guard access to
|
||||
the private key** for your instance identity CA certificate. In CredHub, it can be obtained through the
|
||||
following call: `$ credhub get -n /cf/diego-instance-identity-root-ca`.
|
||||
|
||||
Take extra steps to limit access to that path in CredHub, whether it be through use of CredHub's ACL system,
|
||||
or through carefully limiting the users who can access CredHub.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Obtaining Your Instance Identity CA Certificate
|
||||
|
||||
In most versions of PCF, instance identity is enabled out-of-the-box. Check by pulling your CA certificate,
|
||||
which you'll need to configure this auth engine. There are undoubtedly multiple ways to do this, but this
|
||||
is how we did it.
|
||||
|
||||
#### From CF Dev
|
||||
|
||||
```
|
||||
$ bosh int --path /diego_instance_identity_ca ~/.cfdev/state/bosh/creds.yml
|
||||
```
|
||||
|
||||
#### From CredHub
|
||||
|
||||
[Install and authenticate to the PCF command line tool](https://docs.pivotal.io/tiledev/2-2/pcf-command.html),
|
||||
and [install jq](https://stedolan.github.io/jq/).
|
||||
|
||||
Get the credentials you'll use for CredHub:
|
||||
```
|
||||
$ pcf settings | jq '.products[0].director_credhub_client_credentials'
|
||||
```
|
||||
|
||||
SSH into your Ops Manager VM:
|
||||
```
|
||||
ssh -i ops_mgr.pem ubuntu@$OPS_MGR_URL
|
||||
```
|
||||
Please note that the above `OPS_MGR_URL` shouldn't be prepended with `https://`.
|
||||
|
||||
Log in to Credhub using the credentials you obtained earlier:
|
||||
```
|
||||
$ credhub login --client-name=director_to_credhub --client-secret=CoJPkrsYi3c-Fx2QHEEDyaEEUuOfYMzw
|
||||
```
|
||||
6. Retrieve the CA information:
|
||||
```
|
||||
$ credhub get -n /cf/diego-instance-identity-root-ca
|
||||
```
|
||||
7. You'll receive a response like:
|
||||
```
|
||||
id: be2bd996-1d35-443b-b81c-90095024d5e7
|
||||
name: /cf/diego-instance-identity-root-ca
|
||||
type: certificate
|
||||
value:
|
||||
ca: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNDCCAhygAwIBAgITPqTy1qvfHNEVuxsl9l1glY85OTANBgkqhkiG9w0BAQsF
|
||||
ADAqMSgwJgYDVQQDEx9EaWVnbyBJbnN0YW5jZSBJZGVudGl0eSBSb290IENBMB4X
|
||||
DTE5MDYwNjA5MTIwMVoXDTIyMDYwNTA5MTIwMVowKjEoMCYGA1UEAxMfRGllZ28g
|
||||
SW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBALa8xGDYT/q3UzEKAsLDajhuHxPpIPFlCXwp6u8U5Qrf427Xof7n
|
||||
rXRKzRu3g7E20U/OwzgBi3VZs8T29JGNWeA2k0HtX8oQ+Wc8Qngz9M8t1h9SZlx5
|
||||
fGfxPt3x7xozaIGJ8p4HKQH1ZlirL7dzun7Y+7m6Ey8cMVsepqUs64r8+KpCbxKJ
|
||||
rV04qtTNlr0LG3yOxSHlip+DDvUVL3jSFz/JDWxwCymiFBAh0QjG1LKp2FisURoX
|
||||
GY+HJbf2StpK3i4dYnxQXQlMDpipozK7WFxv3gH4Q6YMZvlmIPidAF8FxfDIsYcq
|
||||
TgQ5q0pr9mbu8oKbZ74vyZMqiy+r9vLhbu0CAwEAAaNTMFEwHQYDVR0OBBYEFAHf
|
||||
pwqBhZ8/A6ZAvU+p5JPz/omjMB8GA1UdIwQYMBaAFAHfpwqBhZ8/A6ZAvU+p5JPz
|
||||
/omjMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADuDJev+6bOC
|
||||
v7t9SS4Nd/zeREuF9IKsHDHrYUZBIO1aBQbOO1iDtL4VA3LBEx6fOgN5fbxroUsz
|
||||
X9/6PtxLe+5U8i5MOztK+OxxPrtDfnblXVb6IW4EKhTnWesS7R2WnOWtzqRQXKFU
|
||||
voBn3QckLV1o9eqzYIE/aob4z0GaVanA9PSzzbVPsX79RCD1B7NmV0cKEQ7IrCrh
|
||||
L7ElDV/GlNrtVdHjY0mwz9iu+0YJvxvcHDTERi106b28KXzJz+P5/hyg2wqRXzdI
|
||||
faXAjW0kuq5nxyJUALwxD/8pz77uNt4w6WfJoSDM6XrAIhh15K3tZg9EzBmAZ/5D
|
||||
jK0RcmCyaXw=
|
||||
-----END CERTIFICATE-----
|
||||
certificate: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNDCCAhygAwIBAgITPqTy1qvfHNEVuxsl9l1glY85OTANBgkqhkiG9w0BAQsF
|
||||
ADAqMSgwJgYDVQQDEx9EaWVnbyBJbnN0YW5jZSBJZGVudGl0eSBSb290IENBMB4X
|
||||
DTE5MDYwNjA5MTIwMVoXDTIyMDYwNTA5MTIwMVowKjEoMCYGA1UEAxMfRGllZ28g
|
||||
SW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBALa8xGDYT/q3UzEKAsLDajhuHxPpIPFlCXwp6u8U5Qrf427Xof7n
|
||||
rXRKzRu3g7E20U/OwzgBi3VZs8T29JGNWeA2k0HtX8oQ+Wc8Qngz9M8t1h9SZlx5
|
||||
fGfxPt3x7xozaIGJ8p4HKQH1ZlirL7dzun7Y+7m6Ey8cMVsepqUs64r8+KpCbxKJ
|
||||
rV04qtTNlr0LG3yOxSHlip+DDvUVL3jSFz/JDWxwCymiFBAh0QjG1LKp2FisURoX
|
||||
GY+HJbf2StpK3i4dYnxQXQlMDpipozK7WFxv3gH4Q6YMZvlmIPidAF8FxfDIsYcq
|
||||
TgQ5q0pr9mbu8oKbZ74vyZMqiy+r9vLhbu0CAwEAAaNTMFEwHQYDVR0OBBYEFAHf
|
||||
pwqBhZ8/A6ZAvU+p5JPz/omjMB8GA1UdIwQYMBaAFAHfpwqBhZ8/A6ZAvU+p5JPz
|
||||
/omjMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADuDJev+6bOC
|
||||
v7t9SS4Nd/zeREuF9IKsHDHrYUZBIO1aBQbOO1iDtL4VA3LBEx6fOgN5fbxroUsz
|
||||
X9/6PtxLe+5U8i5MOztK+OxxPrtDfnblXVb6IW4EKhTnWesS7R2WnOWtzqRQXKFU
|
||||
voBn3QckLV1o9eqzYIE/aob4z0GaVanA9PSzzbVPsX79RCD1B7NmV0cKEQ7IrCrh
|
||||
L7ElDV/GlNrtVdHjY0mwz9iu+0YJvxvcHDTERi106b28KXzJz+P5/hyg2wqRXzdI
|
||||
faXAjW0kuq5nxyJUALwxD/8pz77uNt4w6WfJoSDM6XrAIhh15K3tZg9EzBmAZ/5D
|
||||
jK0RcmCyaXw=
|
||||
-----END CERTIFICATE-----
|
||||
private_key: |
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAtrzEYNhP+rdTMQoCwsNqOG4fE+kg8WUJfCnq7xTlCt/jbteh
|
||||
/uetdErNG7eDsTbRT87DOAGLdVmzxPb0kY1Z4DaTQe1fyhD5ZzxCeDP0zy3WH1Jm
|
||||
XHl8Z/E+3fHvGjNogYnyngcpAfVmWKsvt3O6ftj7uboTLxwxWx6mpSzrivz4qkJv
|
||||
EomtXTiq1M2WvQsbfI7FIeWKn4MO9RUveNIXP8kNbHALKaIUECHRCMbUsqnYWKxR
|
||||
GhcZj4clt/ZK2kreLh1ifFBdCUwOmKmjMrtYXG/eAfhDpgxm+WYg+J0AXwXF8Mix
|
||||
hypOBDmrSmv2Zu7ygptnvi/JkyqLL6v28uFu7QIDAQABAoIBACGIPhjvWK3PGirz
|
||||
hVIr/b/hJT7IFs11Fup73qqEkQsPznI2i3l1FfUzDLQ7VqUcRAh7DoOmdOrRzRUl
|
||||
o/dZktZ77UW5w0wXFU0GV8Qq9I9X/+S7gCEUAeoo8LpVfOS37kNnBuhMtA+x8lfv
|
||||
AdCOIfjI5FhOdtq8N6pa04WX2pkkRzQkIpneRcLPqq1WwKZK1o8zxCnbP+4SI8yo
|
||||
dTB2ldDY+1vusMYoFch2IsPDMCxVUpAYOoO0jvyOM4cqm0m7P+Tb6Qy19GG50ZfC
|
||||
PlEK76YTOurdirGWdnGC+JEf4smrGgIvJGKEb55/qbqPow/o6Bf4XVXnOHaboW/W
|
||||
Mu4cGFkCgYEA2JaQxFF51yuz4W7/VFZCCix3woPH12fZ3fz7aVMUfn4WQwMbNLVa
|
||||
7G4gdRclReOs5FLM7jPqiTxDckXnoWRc5Ff9Xn4c2ioXrqzXr5V4qJ4GAWxAx0uM
|
||||
w1u5ZpVL2HO12pat74MnYw7EJ65oznQNFSC1FAGn5BJ9f5HFk4X3ngMCgYEA1/1Q
|
||||
XmAk1XJUQ0RP0EehwNipZQPhodGmrqHBGED+N/8+eRMo4shi//EcPXZ222j9kqAE
|
||||
inPA9qaDxhBjgMt+JBFkj/bmTO/Yz8XusBBa5YlN9Ev30zlO+dRlM41/piluPTzf
|
||||
vNQuzyNIzl2Gzd71R1TcuFWIDxn8BR0/cBA/5E8CgYAT7m8uEc1jlrr8AOnwSevT
|
||||
4dm3hccLNJxhCFnejG2zYkkMK6oCRLo0TcIg5Ftivhv3+wKu3Qo1TN1sE7DIMmM2
|
||||
BD7lxjdDgGIjifZjSx8KbVhiIyMm8/XlOHisTwrmxWcz0W/6PZiPThmRCUTN0vIt
|
||||
QpBHYgugOm9gIPsMo2RxHwKBgQDOUDjZvUrR3GCi1HjMwe+/bvX3+MopMULfYsE4
|
||||
srRittxs+KFAZxsx0ZUhHKySDurQiSttOP6kXBBZPERfvYFjYH3HipcX/K8EYNQL
|
||||
t8OrqAkfhwVV7VMEDx8QLGQ3SzHzKteo3qFL2S9teCcRNZzjoysmpQTPMAnstLBp
|
||||
EgyFvwKBgQDObNn/Kmfwi6TuGhIjLtBuUEhp5n4EUtysTUZs/15h02MWOfI8CCvm
|
||||
xWb6/vZrVggxGlZgZtKy9+COPVpEMFaVdwq9uq4lW77sSBwGIwfzHd1CIjce6mSg
|
||||
P5+wO3aTgvr4n8D5NyWcnYPJKRQzqWHHnfk+9TQA1l0g3/yQXfCx2A==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
version_created_at: "2019-06-06T09:12:01Z"
|
||||
```
|
||||
|
||||
From that response, copy the first certificate (under `ca: |`) and place
|
||||
it into its own separate file using a plain text editor like [Sublime](https://www.sublimetext.com/).
|
||||
The following instructions assume you name the file `ca.crt`.
|
||||
Remove any tabs before each line, and any trailing space or lines. When
|
||||
complete, your CA certificate should look like this:
|
||||
|
||||
```
|
||||
$ cat ca.crt
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNDCCAhygAwIBAgITPqTy1qvfHNEVuxsl9l1glY85OTANBgkqhkiG9w0BAQsF
|
||||
ADAqMSgwJgYDVQQDEx9EaWVnbyBJbnN0YW5jZSBJZGVudGl0eSBSb290IENBMB4X
|
||||
DTE5MDYwNjA5MTIwMVoXDTIyMDYwNTA5MTIwMVowKjEoMCYGA1UEAxMfRGllZ28g
|
||||
SW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBALa8xGDYT/q3UzEKAsLDajhuHxPpIPFlCXwp6u8U5Qrf427Xof7n
|
||||
rXRKzRu3g7E20U/OwzgBi3VZs8T29JGNWeA2k0HtX8oQ+Wc8Qngz9M8t1h9SZlx5
|
||||
fGfxPt3x7xozaIGJ8p4HKQH1ZlirL7dzun7Y+7m6Ey8cMVsepqUs64r8+KpCbxKJ
|
||||
rV04qtTNlr0LG3yOxSHlip+DDvUVL3jSFz/JDWxwCymiFBAh0QjG1LKp2FisURoX
|
||||
GY+HJbf2StpK3i4dYnxQXQlMDpipozK7WFxv3gH4Q6YMZvlmIPidAF8FxfDIsYcq
|
||||
TgQ5q0pr9mbu8oKbZ74vyZMqiy+r9vLhbu0CAwEAAaNTMFEwHQYDVR0OBBYEFAHf
|
||||
pwqBhZ8/A6ZAvU+p5JPz/omjMB8GA1UdIwQYMBaAFAHfpwqBhZ8/A6ZAvU+p5JPz
|
||||
/omjMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADuDJev+6bOC
|
||||
v7t9SS4Nd/zeREuF9IKsHDHrYUZBIO1aBQbOO1iDtL4VA3LBEx6fOgN5fbxroUsz
|
||||
X9/6PtxLe+5U8i5MOztK+OxxPrtDfnblXVb6IW4EKhTnWesS7R2WnOWtzqRQXKFU
|
||||
voBn3QckLV1o9eqzYIE/aob4z0GaVanA9PSzzbVPsX79RCD1B7NmV0cKEQ7IrCrh
|
||||
L7ElDV/GlNrtVdHjY0mwz9iu+0YJvxvcHDTERi106b28KXzJz+P5/hyg2wqRXzdI
|
||||
faXAjW0kuq5nxyJUALwxD/8pz77uNt4w6WfJoSDM6XrAIhh15K3tZg9EzBmAZ/5D
|
||||
jK0RcmCyaXw=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
Verify that this certificate can be properly parsed like so:
|
||||
|
||||
```
|
||||
$ openssl x509 -in ca.crt -text -noout
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
3e:a4:f2:d6:ab:df:1c:d1:15:bb:1b:25:f6:5d:60:95:8f:39:39
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: CN=Diego Instance Identity Root CA
|
||||
Validity
|
||||
Not Before: Jun 6 09:12:01 2019 GMT
|
||||
Not After : Jun 5 09:12:01 2022 GMT
|
||||
Subject: CN=Diego Instance Identity Root CA
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:b6:bc:c4:60:d8:4f:fa:b7:53:31:0a:02:c2:c3:
|
||||
6a:38:6e:1f:13:e9:20:f1:65:09:7c:29:ea:ef:14:
|
||||
e5:0a:df:e3:6e:d7:a1:fe:e7:ad:74:4a:cd:1b:b7:
|
||||
83:b1:36:d1:4f:ce:c3:38:01:8b:75:59:b3:c4:f6:
|
||||
f4:91:8d:59:e0:36:93:41:ed:5f:ca:10:f9:67:3c:
|
||||
42:78:33:f4:cf:2d:d6:1f:52:66:5c:79:7c:67:f1:
|
||||
3e:dd:f1:ef:1a:33:68:81:89:f2:9e:07:29:01:f5:
|
||||
66:58:ab:2f:b7:73:ba:7e:d8:fb:b9:ba:13:2f:1c:
|
||||
31:5b:1e:a6:a5:2c:eb:8a:fc:f8:aa:42:6f:12:89:
|
||||
ad:5d:38:aa:d4:cd:96:bd:0b:1b:7c:8e:c5:21:e5:
|
||||
8a:9f:83:0e:f5:15:2f:78:d2:17:3f:c9:0d:6c:70:
|
||||
0b:29:a2:14:10:21:d1:08:c6:d4:b2:a9:d8:58:ac:
|
||||
51:1a:17:19:8f:87:25:b7:f6:4a:da:4a:de:2e:1d:
|
||||
62:7c:50:5d:09:4c:0e:98:a9:a3:32:bb:58:5c:6f:
|
||||
de:01:f8:43:a6:0c:66:f9:66:20:f8:9d:00:5f:05:
|
||||
c5:f0:c8:b1:87:2a:4e:04:39:ab:4a:6b:f6:66:ee:
|
||||
f2:82:9b:67:be:2f:c9:93:2a:8b:2f:ab:f6:f2:e1:
|
||||
6e:ed
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
01:DF:A7:0A:81:85:9F:3F:03:A6:40:BD:4F:A9:E4:93:F3:FE:89:A3
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:01:DF:A7:0A:81:85:9F:3F:03:A6:40:BD:4F:A9:E4:93:F3:FE:89:A3
|
||||
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
3b:83:25:eb:fe:e9:b3:82:bf:bb:7d:49:2e:0d:77:fc:de:44:
|
||||
4b:85:f4:82:ac:1c:31:eb:61:46:41:20:ed:5a:05:06:ce:3b:
|
||||
58:83:b4:be:15:03:72:c1:13:1e:9f:3a:03:79:7d:bc:6b:a1:
|
||||
4b:33:5f:df:fa:3e:dc:4b:7b:ee:54:f2:2e:4c:3b:3b:4a:f8:
|
||||
ec:71:3e:bb:43:7e:76:e5:5d:56:fa:21:6e:04:2a:14:e7:59:
|
||||
eb:12:ed:1d:96:9c:e5:ad:ce:a4:50:5c:a1:54:be:80:67:dd:
|
||||
07:24:2d:5d:68:f5:ea:b3:60:81:3f:6a:86:f8:cf:41:9a:55:
|
||||
a9:c0:f4:f4:b3:cd:b5:4f:b1:7e:fd:44:20:f5:07:b3:66:57:
|
||||
47:0a:11:0e:c8:ac:2a:e1:2f:b1:25:0d:5f:c6:94:da:ed:55:
|
||||
d1:e3:63:49:b0:cf:d8:ae:fb:46:09:bf:1b:dc:1c:34:c4:46:
|
||||
2d:74:e9:bd:bc:29:7c:c9:cf:e3:f9:fe:1c:a0:db:0a:91:5f:
|
||||
37:48:7d:a5:c0:8d:6d:24:ba:ae:67:c7:22:54:00:bc:31:0f:
|
||||
ff:29:cf:be:ee:36:de:30:e9:67:c9:a1:20:cc:e9:7a:c0:22:
|
||||
18:75:e4:ad:ed:66:0f:44:cc:19:80:67:fe:43:8c:ad:11:72:
|
||||
60:b2:69:7c
|
||||
```
|
||||
|
||||
Congratulations! You have obtained the CA certificate you'll use for configuring
|
||||
this auth engine.
|
||||
|
||||
### Obtaining Your API Credentials
|
||||
|
||||
From the directory where you added `metadata` in the previous step to authenticate to the pcf command-line
|
||||
tool, run the following commands:
|
||||
|
||||
```
|
||||
$ pcf target
|
||||
$ cf api
|
||||
```
|
||||
|
||||
The api endpoint given will be used for configuring this Vault auth method.
|
||||
|
||||
This plugin was tested with Org Manager level permissions, but lower level permissions may be usable.
|
||||
```
|
||||
$ cf create-user vault pa55word
|
||||
$ cf orgs
|
||||
$ cf org-users my-example-org
|
||||
$ cf set-org-role Alice my-example-org OrgManager
|
||||
```
|
||||
|
||||
Since the PCF API tends to use a self-signed certificate, you'll also need to configure
|
||||
Vault to trust that certificate. You can obtain its API certificate via:
|
||||
|
||||
```
|
||||
openssl s_client -showcerts -servername domain.com -connect domain.com:443
|
||||
```
|
||||
|
||||
You'll see a certificate outputted as part of the response, which should be broken
|
||||
out into a separate well-formatted file like the `ca.crt` above, and used for the
|
||||
`pcf_api_trusted_certificates` field.
|
||||
|
||||
## Downloading the Plugin
|
||||
|
||||
- `$ git clone git@github.com:hashicorp/vault-plugin-auth-pcf.git`
|
||||
- `$ cd vault-plugin-auth-pcf`
|
||||
- `$ PCF_HOME=$(pwd)`
|
||||
|
||||
## Sample Usage
|
||||
|
||||
Please note that this example uses `generate-signature`, a tool installed through `$ make tools`.
|
||||
|
||||
First, enable the PCF auth engine.
|
||||
```
|
||||
$ vault auth enable pcf
|
||||
```
|
||||
|
||||
Next, configure the plugin. In the `config` call below, `certificates` is intended to be the instance
|
||||
identity CA certificate you pulled above.
|
||||
|
||||
In the CF Dev environment the default API address is `https://api.dev.cfdev.sh`. The default username and password
|
||||
are `admin`, `admin`. In a production environment, these attributes will vary.
|
||||
```
|
||||
$ vault write auth/pcf/config \
|
||||
certificates=@ca.crt \
|
||||
pcf_api_addr=https://api.dev.cfdev.sh \
|
||||
pcf_username=admin \
|
||||
pcf_password=admin \
|
||||
pcf_api_trusted_certificates=@pcfapi.crt
|
||||
```
|
||||
|
||||
Then, add a role that will be used to grant specific Vault policies to those logging in with it. When a constraint like
|
||||
`bound_application_ids` is added, then the application ID on the cert used for logging in _must_ be one of the role's
|
||||
application IDs. However, if `bound_application_ids` is omitted, then _any_ application ID will match. We recommend
|
||||
configuring as many bound parameters as possible.
|
||||
|
||||
Also, by default, the IP address on the certificate presented at login must match that of the caller. However, if
|
||||
your callers tend to be proxied, this may not work for you. If that's the case, set `disable_ip_matching` to true.
|
||||
```
|
||||
$ vault write auth/pcf/roles/test-role \
|
||||
bound_application_ids=2d3e834a-3a25-4591-974c-fa5626d5d0a1 \
|
||||
bound_space_ids=3d2eba6b-ef19-44d5-91dd-1975b0db5cc9 \
|
||||
bound_organization_ids=34a878d0-c2f9-4521-ba73-a9f664e82c7bf \
|
||||
policies=foo-policies
|
||||
```
|
||||
|
||||
Logging in is intended to be performed using your `CF_INSTANCE_CERT` and `CF_INSTANCE_KEY`. This is an example of how
|
||||
it can be done.
|
||||
```
|
||||
$ export CF_INSTANCE_CERT=$PCF_HOME/testdata/fake-certificates/instance.crt
|
||||
$ export CF_INSTANCE_KEY=$PCF_HOME/testdata/fake-certificates/instance.key
|
||||
$ vault login -method=pcf role=test-role
|
||||
```
|
||||
|
||||
### Updating the CA Certificate
|
||||
|
||||
In PCF, most CA certificates expire after 4 years. However, it's possible to configure your own CA certificate for the
|
||||
instance identity service, and its expiration date could vary. Either way, sometimes CA certificates expire and it may
|
||||
be necessary to have multiple configured so the beginning date of once commences when another expires.
|
||||
|
||||
To configure multiple certificates, simply update the config to include the current one and future one.
|
||||
```
|
||||
$ CURRENT=$(cat /path/to/current-ca.crt)
|
||||
$ FUTURE=$(cat /path/to/future-ca.crt)
|
||||
$ vault write auth/vault-plugin-auth-pcf/config certificates="$CURRENT,$FUTURE"
|
||||
```
|
||||
|
||||
All other configured values will remain untouched; however, the previous value for `certificates` will be overwritten
|
||||
with the new one you've provided.
|
||||
|
||||
Providing a future CA certificate before the current one expires can protect you from having a downtime while the service
|
||||
is switching over from the old to the new. If a client certificate was issued by _any_ CA certificate you've configured,
|
||||
login will succeed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Obtaining a Certificate Error from the PCF API
|
||||
|
||||
When configuring this plugin, you may encounter an error like:
|
||||
```
|
||||
Error writing data to auth/pcf/config: Error making API request.
|
||||
|
||||
URL: PUT http://127.0.0.1:8200/v1/auth/pcf/config
|
||||
Code: 500. Errors:
|
||||
|
||||
* 1 error occurred:
|
||||
* unable to establish an initial connection to the PCF API: Could not get api /v2/info: Get https://api.sys.lagunaniguel.cf-app.com/v2/info: x509: certificate signed by unknown authority
|
||||
```
|
||||
|
||||
To resolve this error, review instructions above regarding setting the `pcf_api_trusted_certificates` field.
|
||||
|
||||
### verify-certs
|
||||
|
||||
This tool, installed by `make tools`, is for verifying that your CA certificate, client certificate, and client
|
||||
key are all properly related to each other and will pass verification if used by this auth engine. If you're
|
||||
debugging authentication problems that may be related to your certificates, it's a fantastic tool to use.
|
||||
|
||||
```
|
||||
verify-certs -ca-cert=local/path/to/ca.crt -instance-cert=local/path/to/instance.crt -instance-key=local/path/to/instance.key
|
||||
```
|
||||
The `ca-cert` should be the cert that was used to issue the given client certificate.
|
||||
|
||||
The `instance-cert` given should be the value for the `CF_INSTANCE_CERT` variable in the PCF environment you're
|
||||
using, and the `instance-key` should be the value for the `CF_INSTANCE_KEY`.
|
||||
|
||||
The tool does take the _local path to_ these certificates, so you'll need to gather them and place them on your
|
||||
local machine to verify they all will work together.
|
||||
|
||||
### generate-signature
|
||||
|
||||
This tool, installed by `make tools`, is for generating a valid signature to be used for signing into Vault via PCF.
|
||||
|
||||
It can be used as a standalone tool for generating a signature like so:
|
||||
```
|
||||
export CF_INSTANCE_CERT=path/to/instance.crt
|
||||
export CF_INSTANCE_KEY=path/to/instance.key
|
||||
export SIGNING_TIME=$(date -u)
|
||||
export ROLE='test-role'
|
||||
generate-signature
|
||||
```
|
||||
|
||||
It can also be used for signing into Vault like so:
|
||||
```
|
||||
export CF_INSTANCE_CERT=path/to/instance.crt
|
||||
export CF_INSTANCE_KEY=path/to/instance.key
|
||||
export SIGNING_TIME=$(date -u)
|
||||
export ROLE='test-role'
|
||||
|
||||
vault write auth/vault-plugin-auth-pcf/login \
|
||||
role=$ROLE \
|
||||
certificate=$CF_INSTANCE_CERT \
|
||||
signing-time=SIGNING_TIME \
|
||||
signature=$(generate-signature)
|
||||
```
|
||||
If the tool is being run in a PCF environment already containing the `CF_INSTANCE_CERT` and `CF_INSTANCE_KEY`, those
|
||||
variables obviously won't need to be manually set before the tool is used and can just be pulled as they are.
|
||||
|
||||
## Developing
|
||||
|
||||
### mock-pcf-server
|
||||
|
||||
This tool, installed by `make tools`, is for use in development. It lets you run a mocked PCF server for use in local
|
||||
testing, with output that can be used as the `pcf_api_addr`, `pcf_username`, and `pcf_password` in your config.
|
||||
|
||||
Example use:
|
||||
```
|
||||
$ mock-pcf-server
|
||||
running at http://127.0.0.1:33671
|
||||
username is username
|
||||
password is password
|
||||
```
|
||||
|
||||
Simply hit CTRL+C to stop the test server.
|
||||
|
||||
### Implementing the Signature Algorithm in Other Languages
|
||||
|
||||
The signing algorithm used by this plugin is viewable in `signatures/version1.go`. There is also a test
|
||||
called `TestSignature` in the same package that outputs a viewable signing string, hash of it, and
|
||||
resulting signature. The signature will be different every time the test is run because some
|
||||
of the input to the final signature includes cryptographically random material. This means that no matter
|
||||
what you do, your final signature won't match any signatures shown; the important thing, however, is that
|
||||
it can be verified as having been signed by the private key that's associated with the given client
|
||||
certificate.
|
||||
|
||||
To develop your own version of the signing algorithm in a different language, we recommend you duplicate
|
||||
the inputs to `TestSignature`, duplicate its signing string and hash, and duplicate the signing algorithm used.
|
||||
|
||||
### Quick Start
|
||||
|
||||
```
|
||||
# After cloning the repo, generate fake certs, a test binary, and install the tools.
|
||||
make test
|
||||
make dev
|
||||
make tools
|
||||
|
||||
# In one shell window, run Vault with the plugin available in the catalog.
|
||||
vault server -dev -dev-root-token-id=root -dev-plugin-dir=$PCF_HOME/bin -log-level=debug
|
||||
|
||||
# In another shell window, run a mock of the PCF API so the plugin's client calls won't fail.
|
||||
mock-pcf-server
|
||||
|
||||
# In another shell window, execute the following commands to exercise each endpoint.
|
||||
export VAULT_ADDR=http://localhost:8200
|
||||
export VAULT_TOKEN=root
|
||||
export MOCK_PCF_SERVER_ADDR='something' # ex. http://127.0.0.1:32937
|
||||
|
||||
vault auth enable vault-plugin-auth-pcf
|
||||
|
||||
vault write auth/vault-plugin-auth-pcf/config \
|
||||
certificates=@$PCF_HOME/testdata/fake-certificates/ca.crt \
|
||||
pcf_api_addr=$MOCK_PCF_SERVER_ADDR \
|
||||
pcf_username=username \
|
||||
pcf_password=password
|
||||
|
||||
vault write auth/vault-plugin-auth-pcf/roles/test-role \
|
||||
bound_application_ids=2d3e834a-3a25-4591-974c-fa5626d5d0a1 \
|
||||
bound_space_ids=3d2eba6b-ef19-44d5-91dd-1975b0db5cc9 \
|
||||
bound_organization_ids=34a878d0-c2f9-4521-ba73-a9f664e82c7bf \
|
||||
bound_instance_ids=1bf2e7f6-2d1d-41ec-501c-c70 \
|
||||
policies=foo,policies \
|
||||
disable_ip_matching=true \
|
||||
ttl=86400s \
|
||||
max_ttl=86400s \
|
||||
period=86400s
|
||||
|
||||
export CF_INSTANCE_CERT=$PCF_HOME/testdata/fake-certificates/instance.crt
|
||||
export CF_INSTANCE_KEY=$PCF_HOME/testdata/fake-certificates/instance.key
|
||||
export SIGNING_TIME=$(date -u)
|
||||
export ROLE='test-role'
|
||||
vault write auth/vault-plugin-auth-pcf/login \
|
||||
role=$ROLE \
|
||||
certificate=@$CF_INSTANCE_CERT \
|
||||
signing_time="$SIGNING_TIME" \
|
||||
signature=$(generate-signature)
|
||||
|
||||
vault token renew <token>
|
||||
|
||||
CURRENT=$(cat $PCF_HOME/testdata/fake-certificates/ca.crt)
|
||||
FUTURE=$(cat $PCF_HOME/testdata/fake-certificates/ca.crt)
|
||||
vault write auth/vault-plugin-auth-pcf/config certificates="$CURRENT,$FUTURE"
|
||||
```
|
|
@ -0,0 +1,49 @@
|
|||
package pcf
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
const (
|
||||
// These env vars are used frequently to pull the client certificate and private key
|
||||
// from PCF containers; thus are placed here for ease of discovery and use from
|
||||
// outside packages.
|
||||
EnvVarInstanceCertificate = "CF_INSTANCE_CERT"
|
||||
EnvVarInstanceKey = "CF_INSTANCE_KEY"
|
||||
)
|
||||
|
||||
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
||||
b := &backend{}
|
||||
b.Backend = &framework.Backend{
|
||||
AuthRenew: b.pathLoginRenew,
|
||||
Help: backendHelp,
|
||||
PathsSpecial: &logical.Paths{
|
||||
SealWrapStorage: []string{"config"},
|
||||
Unauthenticated: []string{"login"},
|
||||
},
|
||||
Paths: []*framework.Path{
|
||||
b.pathConfig(),
|
||||
b.pathListRoles(),
|
||||
b.pathRoles(),
|
||||
b.pathLogin(),
|
||||
},
|
||||
BackendType: logical.TypeCredential,
|
||||
}
|
||||
if err := b.Setup(ctx, conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
*framework.Backend
|
||||
}
|
||||
|
||||
const backendHelp = `
|
||||
The PCF auth backend supports logging in using PCF's identity service.
|
||||
Once a CA certificate is configured, and Vault is configured to consume
|
||||
PCF's API, PCF's instance identity credentials can be used to authenticate.'
|
||||
`
|
|
@ -0,0 +1,115 @@
|
|||
package pcf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/signatures"
|
||||
"github.com/hashicorp/vault/api"
|
||||
)
|
||||
|
||||
type CLIHandler struct{}
|
||||
|
||||
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, error) {
|
||||
mount, ok := m["mount"]
|
||||
if !ok {
|
||||
mount = "pcf"
|
||||
}
|
||||
|
||||
role := m["role"]
|
||||
if role == "" {
|
||||
return nil, errors.New(`"role" is required`)
|
||||
}
|
||||
|
||||
pathToInstanceCert := m["cf_instance_cert"]
|
||||
if pathToInstanceCert == "" {
|
||||
pathToInstanceCert = os.Getenv(EnvVarInstanceCertificate)
|
||||
}
|
||||
if pathToInstanceCert == "" {
|
||||
return nil, errors.New(`"cf_instance_cert" is required`)
|
||||
}
|
||||
|
||||
pathToInstanceKey := m["cf_instance_key"]
|
||||
if pathToInstanceKey == "" {
|
||||
pathToInstanceKey = os.Getenv(EnvVarInstanceKey)
|
||||
}
|
||||
if pathToInstanceKey == "" {
|
||||
return nil, errors.New(`"cf_instance_key" is required`)
|
||||
}
|
||||
|
||||
certBytes, err := ioutil.ReadFile(pathToInstanceCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfInstanceCertContents := string(certBytes)
|
||||
|
||||
signingTime := time.Now().UTC()
|
||||
signatureData := &signatures.SignatureData{
|
||||
SigningTime: signingTime,
|
||||
Role: role,
|
||||
CFInstanceCertContents: cfInstanceCertContents,
|
||||
}
|
||||
signature, err := signatures.Sign(pathToInstanceKey, signatureData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loginData := map[string]interface{}{
|
||||
"role": role,
|
||||
"cf_instance_cert": cfInstanceCertContents,
|
||||
"signing_time": signingTime.Format(signatures.TimeFormat),
|
||||
"signature": signature,
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("auth/%s/login", mount)
|
||||
|
||||
secret, err := c.Logical().Write(path, loginData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, errors.New("empty response from credential provider")
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (h *CLIHandler) Help() string {
|
||||
help := `
|
||||
Usage: vault login -method=pcf [CONFIG K=V...]
|
||||
|
||||
The PCF auth method allows users to authenticate using PCF's instance identity service.
|
||||
|
||||
The PCF credentials may be specified explicitly via the command line:
|
||||
|
||||
$ vault login -method=pcf role=...
|
||||
|
||||
This will automatically pull from the CF_INSTANCE_CERT and CF_INSTANCE_KEY values
|
||||
in your local environment. If they're not available or you wish to override them,
|
||||
they may also be supplied explicitly:
|
||||
|
||||
$ vault login -method=pcf role=... cf_instance_cert=... cf_instance_key=...
|
||||
|
||||
Configuration:
|
||||
|
||||
cf_instance_cert=<string>
|
||||
Explicit value to use for the path to the PCF instance certificate.
|
||||
|
||||
cf_instance_key=<string>
|
||||
Explicit value to use for the path to the PCF instance key.
|
||||
|
||||
mount=<string>
|
||||
Path where the PCF credential method is mounted. This is usually provided
|
||||
via the -path flag in the "vault login" command, but it can be specified
|
||||
here as well. If specified here, it takes precedence over the value for
|
||||
-path. The default value is "pcf".
|
||||
|
||||
role=<string>
|
||||
Name of the role to request a token against
|
||||
`
|
||||
|
||||
return strings.TrimSpace(help)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
module github.com/hashicorp/vault-plugin-auth-pcf
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
||||
github.com/hashicorp/go-hclog v0.9.2
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/hashicorp/go-sockaddr v1.0.2
|
||||
github.com/hashicorp/go-uuid v1.0.1
|
||||
github.com/hashicorp/vault/api v1.0.2
|
||||
github.com/hashicorp/vault/sdk v0.1.11
|
||||
github.com/pkg/errors v0.8.1
|
||||
)
|
|
@ -0,0 +1,168 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v1.0.0 h1:/gQ1sNR8/LHpoxKRQq4PmLBuacfZb4tC93e9B30o/7c=
|
||||
github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/vault/api v1.0.2 h1:/V9fULvLwt58vme/6Rkt/p/GtlresQv+Z9E6dgdANhs=
|
||||
github.com/hashicorp/vault/api v1.0.2/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE=
|
||||
github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU=
|
||||
github.com/hashicorp/vault/sdk v0.1.11 h1:15dSaIT8p1Yq4Ac5OnlRGBdI5Ml/cqS84ObdM23kcA0=
|
||||
github.com/hashicorp/vault/sdk v0.1.11/go.mod h1:XF2Bod+ahPWGARnyFq5LfkOZwWwvveR5ptYwJLqK0ZI=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1 h1:VeAkjQVzKLmu+JnFcK96TPbkuaTIqwGGAzQ9hgwPjVg=
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
29
vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/configuration.go
generated
vendored
Normal file
29
vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/configuration.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// Configuration is the config as it's reflected in Vault's storage system.
|
||||
type Configuration struct {
|
||||
// IdentityCACertificates are the CA certificates that should be used for verifying client certificates.
|
||||
IdentityCACertificates []string `json:"identity_ca_certificates"`
|
||||
|
||||
// IdentityCACertificates that, if presented by the PCF API, should be trusted.
|
||||
PCFAPICertificates []string `json:"pcf_api_trusted_certificates"`
|
||||
|
||||
// PCFAPIAddr is the address of PCF's API, ex: "https://api.dev.cfdev.sh" or "http://127.0.0.1:33671"
|
||||
PCFAPIAddr string `json:"pcf_api_addr"`
|
||||
|
||||
// The username for the PCF API.
|
||||
PCFUsername string `json:"pcf_username"`
|
||||
|
||||
// The password for the PCF API.
|
||||
PCFPassword string `json:"pcf_password"`
|
||||
|
||||
// The maximum seconds old a login request's signing time can be.
|
||||
// This is configurable because in some test environments we found as much as 2 hours of clock drift.
|
||||
LoginMaxSecOld time.Duration `json:"login_max_seconds_old"`
|
||||
|
||||
// The maximum seconds ahead a login request's signing time can be.
|
||||
// This is configurable because in some test environments we found as much as 2 hours of clock drift.
|
||||
LoginMaxSecAhead time.Duration `json:"login_max_seconds_ahead"`
|
||||
}
|
98
vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/pcf_cert.go
generated
vendored
Normal file
98
vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/pcf_cert.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewPCFCertificateFromx509 converts a x509 certificate to a valid, well-formed PCF certificate,
|
||||
// erroring if this isn't possible.
|
||||
func NewPCFCertificateFromx509(certificate *x509.Certificate) (*PCFCertificate, error) {
|
||||
if len(certificate.IPAddresses) != 1 {
|
||||
return nil, fmt.Errorf("valid PCF certs have one IP address, but this has %s", certificate.IPAddresses)
|
||||
}
|
||||
|
||||
pcfCert := &PCFCertificate{
|
||||
InstanceID: certificate.Subject.CommonName,
|
||||
IPAddress: certificate.IPAddresses[0],
|
||||
}
|
||||
|
||||
spaces := 0
|
||||
orgs := 0
|
||||
apps := 0
|
||||
for _, ou := range certificate.Subject.OrganizationalUnit {
|
||||
if strings.HasPrefix(ou, "space:") {
|
||||
pcfCert.SpaceID = strings.Split(ou, "space:")[1]
|
||||
spaces++
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ou, "organization:") {
|
||||
pcfCert.OrgID = strings.Split(ou, "organization:")[1]
|
||||
orgs++
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ou, "app:") {
|
||||
pcfCert.AppID = strings.Split(ou, "app:")[1]
|
||||
apps++
|
||||
continue
|
||||
}
|
||||
}
|
||||
if spaces > 1 {
|
||||
return nil, fmt.Errorf("expected 1 space but received %d", spaces)
|
||||
}
|
||||
if orgs > 1 {
|
||||
return nil, fmt.Errorf("expected 1 org but received %d", orgs)
|
||||
}
|
||||
if apps > 1 {
|
||||
return nil, fmt.Errorf("expected 1 app but received %d", apps)
|
||||
}
|
||||
if err := pcfCert.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pcfCert, nil
|
||||
}
|
||||
|
||||
// NewPCFCertificateFromx509 converts the given fields to a valid, well-formed PCF certificate,
|
||||
// erroring if this isn't possible.
|
||||
func NewPCFCertificate(instanceID, orgID, spaceID, appID, ipAddress string) (*PCFCertificate, error) {
|
||||
pcfCert := &PCFCertificate{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
SpaceID: spaceID,
|
||||
AppID: appID,
|
||||
IPAddress: net.ParseIP(ipAddress),
|
||||
}
|
||||
if err := pcfCert.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pcfCert, nil
|
||||
}
|
||||
|
||||
// PCFCertificate isn't intended to be instantiated directly; but rather through one of the New
|
||||
// methods, which contain logic validating that the expected fields exist.
|
||||
type PCFCertificate struct {
|
||||
InstanceID, OrgID, SpaceID, AppID string
|
||||
IPAddress net.IP
|
||||
}
|
||||
|
||||
func (c *PCFCertificate) validate() error {
|
||||
if c.InstanceID == "" {
|
||||
return errors.New("no instance ID on given certificate")
|
||||
}
|
||||
if c.AppID == "" {
|
||||
return errors.New("no app ID on given certificate")
|
||||
}
|
||||
if c.OrgID == "" {
|
||||
return errors.New("no org ID on given certificate")
|
||||
}
|
||||
if c.SpaceID == "" {
|
||||
return errors.New("no space ID on given certificate")
|
||||
}
|
||||
if c.IPAddress.IsUnspecified() {
|
||||
return errors.New("ip address is unspecified")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-sockaddr"
|
||||
)
|
||||
|
||||
// RoleEntry is a role as it's reflected in Vault's storage system.
|
||||
type RoleEntry struct {
|
||||
BoundAppIDs []string `json:"bound_application_ids"`
|
||||
BoundSpaceIDs []string `json:"bound_space_ids"`
|
||||
BoundOrgIDs []string `json:"bound_organization_ids"`
|
||||
BoundInstanceIDs []string `json:"bound_instance_ids"`
|
||||
BoundCIDRs []*sockaddr.SockAddrMarshaler `json:"bound_cidrs"`
|
||||
Policies []string `json:"policies"`
|
||||
DisableIPMatching bool `json:"disable_ip_matching"`
|
||||
TTL time.Duration `json:"ttl"`
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
Period time.Duration `json:"period"`
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
package pcf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/models"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/util"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
const configStorageKey = "config"
|
||||
|
||||
func (b *backend) pathConfig() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "config",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"identity_ca_certificates": {
|
||||
Required: true,
|
||||
Type: framework.TypeStringSlice,
|
||||
DisplayName: "Identity CA Certificates",
|
||||
DisplayValue: `-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----`,
|
||||
Description: "The PEM-format CA certificates that are required to have issued the instance certificates presented for logging in.",
|
||||
},
|
||||
"pcf_api_trusted_certificates": {
|
||||
Type: framework.TypeStringSlice,
|
||||
DisplayName: "PCF API Trusted IdentityCACertificates",
|
||||
DisplayValue: `-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----`,
|
||||
Description: "The PEM-format CA certificates that are acceptable for the PCF API to present.",
|
||||
},
|
||||
"pcf_api_addr": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "PCF API Address",
|
||||
DisplayValue: "https://api.10.244.0.34.xip.io",
|
||||
Description: "PCF’s API address.",
|
||||
},
|
||||
"pcf_username": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "PCF API Username",
|
||||
DisplayValue: "admin",
|
||||
Description: "The username for PCF’s API.",
|
||||
},
|
||||
"pcf_password": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "PCF API Password",
|
||||
DisplayValue: "admin",
|
||||
Description: "The password for PCF’s API.",
|
||||
DisplaySensitive: true,
|
||||
},
|
||||
"login_max_seconds_old": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
DisplayName: "Login Max Seconds Old",
|
||||
DisplayValue: "300",
|
||||
Description: `Duration in seconds for the maximum acceptable age of a "signing_time". Useful for clock drift.
|
||||
Set low to reduce the opportunity for replay attacks.`,
|
||||
Default: 300,
|
||||
},
|
||||
"login_max_seconds_ahead": {
|
||||
Type: framework.TypeInt,
|
||||
DisplayName: "Login Max Seconds Ahead",
|
||||
DisplayValue: "60",
|
||||
Description: `Duration in seconds for the maximum acceptable length in the future a "signing_time" can be. Useful for clock drift.
|
||||
Set low to reduce the opportunity for replay attacks.`,
|
||||
Default: 60,
|
||||
},
|
||||
},
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.CreateOperation: &framework.PathOperation{
|
||||
Callback: b.operationConfigCreateUpdate,
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.operationConfigCreateUpdate,
|
||||
},
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
Callback: b.operationConfigRead,
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
Callback: b.operationConfigDelete,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: pathConfigSyn,
|
||||
HelpDescription: pathConfigDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) operationConfigCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
config, err := config(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
// They're creating a config.
|
||||
identityCACerts := data.Get("identity_ca_certificates").([]string)
|
||||
if len(identityCACerts) == 0 {
|
||||
return logical.ErrorResponse("'identity_ca_certificates' is required"), nil
|
||||
}
|
||||
pcfApiAddr := data.Get("pcf_api_addr").(string)
|
||||
if pcfApiAddr == "" {
|
||||
return logical.ErrorResponse("'pcf_api_addr' is required"), nil
|
||||
}
|
||||
pcfUsername := data.Get("pcf_username").(string)
|
||||
if pcfUsername == "" {
|
||||
return logical.ErrorResponse("'pcf_username' is required"), nil
|
||||
}
|
||||
pcfPassword := data.Get("pcf_password").(string)
|
||||
if pcfPassword == "" {
|
||||
return logical.ErrorResponse("'pcf_password' is required"), nil
|
||||
}
|
||||
pcfApiCertificates := data.Get("pcf_api_trusted_certificates").([]string)
|
||||
|
||||
// Default this to 5 minutes.
|
||||
loginMaxSecOld := 300 * time.Second
|
||||
if raw, ok := data.GetOk("login_max_seconds_old"); ok {
|
||||
loginMaxSecOld = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
|
||||
// Default this to 1 minute.
|
||||
loginMaxSecAhead := 60 * time.Second
|
||||
if raw, ok := data.GetOk("login_max_seconds_ahead"); ok {
|
||||
loginMaxSecAhead = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
config = &models.Configuration{
|
||||
IdentityCACertificates: identityCACerts,
|
||||
PCFAPICertificates: pcfApiCertificates,
|
||||
PCFAPIAddr: pcfApiAddr,
|
||||
PCFUsername: pcfUsername,
|
||||
PCFPassword: pcfPassword,
|
||||
LoginMaxSecOld: loginMaxSecOld,
|
||||
LoginMaxSecAhead: loginMaxSecAhead,
|
||||
}
|
||||
} else {
|
||||
// They're updating a config. Only update the fields that have been sent in the call.
|
||||
if raw, ok := data.GetOk("identity_ca_certificates"); ok {
|
||||
config.IdentityCACertificates = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("pcf_api_trusted_certificates"); ok {
|
||||
config.PCFAPICertificates = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("pcf_api_addr"); ok {
|
||||
config.PCFAPIAddr = raw.(string)
|
||||
}
|
||||
if raw, ok := data.GetOk("pcf_username"); ok {
|
||||
config.PCFUsername = raw.(string)
|
||||
}
|
||||
if raw, ok := data.GetOk("pcf_password"); ok {
|
||||
config.PCFPassword = raw.(string)
|
||||
}
|
||||
if raw, ok := data.GetOk("login_max_seconds_old"); ok {
|
||||
config.LoginMaxSecOld = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
if raw, ok := data.GetOk("login_max_seconds_ahead"); ok {
|
||||
config.LoginMaxSecAhead = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
// To give early and explicit feedback, make sure the config works by executing a test call
|
||||
// and checking that the API version is supported. If they don't have API v2 running, we would
|
||||
// probably expect a timeout of some sort below because it's first called in the NewPCFClient
|
||||
// method.
|
||||
client, err := util.NewPCFClient(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to establish an initial connection to the PCF API: %s", err)
|
||||
}
|
||||
info, err := client.GetInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !strings.HasPrefix(info.APIVersion, "2.") {
|
||||
return nil, fmt.Errorf("the PCF auth plugin only supports version 2.X.X of the PCF API")
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON(configStorageKey, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) operationConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
config, err := config(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"identity_ca_certificates": config.IdentityCACertificates,
|
||||
"pcf_api_trusted_certificates": config.PCFAPICertificates,
|
||||
"pcf_api_addr": config.PCFAPIAddr,
|
||||
"pcf_username": config.PCFUsername,
|
||||
"login_max_seconds_old": config.LoginMaxSecOld / time.Second,
|
||||
"login_max_seconds_ahead": config.LoginMaxSecAhead / time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) operationConfigDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
if err := req.Storage.Delete(ctx, configStorageKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// storedConfig may return nil without error if the user doesn't currently have a config.
|
||||
func config(ctx context.Context, storage logical.Storage) (*models.Configuration, error) {
|
||||
entry, err := storage.Get(ctx, configStorageKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
config := &models.Configuration{}
|
||||
if err := entry.DecodeJSON(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
const pathConfigSyn = `
|
||||
Provide Vault with the CA certificate used to issue all client certificates.
|
||||
`
|
||||
|
||||
const pathConfigDesc = `
|
||||
When a login is attempted using a PCF client certificate, Vault will verify
|
||||
that the client certificate was issued by the CA certificate configured here.
|
||||
Only those passing this check will be able to gain authorization.
|
||||
`
|
|
@ -0,0 +1,382 @@
|
|||
package pcf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/models"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/signatures"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/util"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/cidrutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (b *backend) pathLogin() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "login",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"role": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "Role Name",
|
||||
DisplayValue: "internally-defined-role",
|
||||
Description: "The name of the role to authenticate against.",
|
||||
},
|
||||
"cf_instance_cert": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "CF_INSTANCE_CERT Contents",
|
||||
Description: "The full body of the file available at the CF_INSTANCE_CERT path on the PCF instance.",
|
||||
},
|
||||
"signing_time": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "Signing Time",
|
||||
DisplayValue: "2006-01-02T15:04:05Z",
|
||||
Description: "The date and time used to construct the signature.",
|
||||
},
|
||||
"signature": {
|
||||
Required: true,
|
||||
Type: framework.TypeString,
|
||||
DisplayName: "Signature",
|
||||
Description: "The signature generated by the client certificate's private key.",
|
||||
},
|
||||
},
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.operationLoginUpdate,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: pathLoginSyn,
|
||||
HelpDescription: pathLoginDesc,
|
||||
}
|
||||
}
|
||||
|
||||
// operationLoginUpdate is called by those wanting to gain access to Vault.
|
||||
// They present the instance certificates that should have been issued by the pre-configured
|
||||
// Certificate Authority, and a signature that should have been signed by the instance cert's
|
||||
// private key. If this holds true, there are additional checks verifying everything looks
|
||||
// good before authentication is given.
|
||||
func (b *backend) operationLoginUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
// Grab the time immediately for checking against the request's signingTime.
|
||||
timeReceived := time.Now().UTC()
|
||||
|
||||
roleName := data.Get("role").(string)
|
||||
if roleName == "" {
|
||||
return logical.ErrorResponse("'role-name' is required"), nil
|
||||
}
|
||||
|
||||
signature := data.Get("signature").(string)
|
||||
if signature == "" {
|
||||
return logical.ErrorResponse("'signature' is required"), nil
|
||||
}
|
||||
|
||||
cfInstanceCertContents := data.Get("cf_instance_cert").(string)
|
||||
if cfInstanceCertContents == "" {
|
||||
return logical.ErrorResponse("'cf_instance_cert' is required"), nil
|
||||
}
|
||||
|
||||
signingTimeRaw := data.Get("signing_time").(string)
|
||||
if signingTimeRaw == "" {
|
||||
return logical.ErrorResponse("'signing_time' is required"), nil
|
||||
}
|
||||
signingTime, err := parseTime(signingTimeRaw)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
config, err := config(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, errors.New("no CA is configured for verifying client certificates")
|
||||
}
|
||||
|
||||
// Ensure the time it was signed isn't too far in the past or future.
|
||||
oldestAllowableSigningTime := timeReceived.Add(-1 * config.LoginMaxSecOld)
|
||||
furthestFutureAllowableSigningTime := timeReceived.Add(config.LoginMaxSecAhead)
|
||||
if signingTime.Before(oldestAllowableSigningTime) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("request is too old; signed at %s but received request at %s; allowable seconds old is %d", signingTime, timeReceived, config.LoginMaxSecOld/time.Second)), nil
|
||||
}
|
||||
if signingTime.After(furthestFutureAllowableSigningTime) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("request is too far in the future; signed at %s but received request at %s; allowable seconds in the future is %d", signingTime, timeReceived, config.LoginMaxSecAhead/time.Second)), nil
|
||||
}
|
||||
|
||||
intermediateCert, identityCert, err := util.ExtractCertificates(cfInstanceCertContents)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Ensure the private key used to create the signature matches our identity
|
||||
// certificate, and that it signed the same data as is presented in the body.
|
||||
// This offers some protection against MITM attacks.
|
||||
signingCert, err := signatures.Verify(signature, &signatures.SignatureData{
|
||||
SigningTime: signingTime,
|
||||
Role: roleName,
|
||||
CFInstanceCertContents: cfInstanceCertContents,
|
||||
})
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
// Make sure the identity/signing cert was actually issued by our CA.
|
||||
if err := util.Validate(config.IdentityCACertificates, intermediateCert, identityCert, signingCert); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Read PCF's identity fields from the certificate.
|
||||
pcfCert, err := models.NewPCFCertificateFromx509(signingCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// It may help some users to be able to easily view the incoming certificate information
|
||||
// in an un-encoded format, as opposed to the encoded format that will appear in the Vault
|
||||
// audit logs.
|
||||
if b.Logger().IsDebug() {
|
||||
b.Logger().Debug(fmt.Sprintf("handling login attempt from %+v", pcfCert))
|
||||
}
|
||||
|
||||
// Ensure the pcf certificate meets the role's constraints.
|
||||
role, err := getRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role == nil {
|
||||
return nil, errors.New("no matching role")
|
||||
}
|
||||
|
||||
if err := b.validate(config, role, pcfCert, req.Connection.RemoteAddr); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Everything checks out.
|
||||
return &logical.Response{
|
||||
Auth: &logical.Auth{
|
||||
Period: role.Period,
|
||||
Policies: role.Policies,
|
||||
InternalData: map[string]interface{}{
|
||||
"role": roleName,
|
||||
"instance_id": pcfCert.InstanceID,
|
||||
"ip_address": pcfCert.IPAddress.String(),
|
||||
},
|
||||
DisplayName: pcfCert.InstanceID,
|
||||
LeaseOptions: logical.LeaseOptions{
|
||||
Renewable: true,
|
||||
TTL: role.TTL,
|
||||
MaxTTL: role.MaxTTL,
|
||||
},
|
||||
Alias: &logical.Alias{
|
||||
Name: pcfCert.AppID,
|
||||
Metadata: map[string]string{
|
||||
"org_id": pcfCert.OrgID,
|
||||
"app_id": pcfCert.AppID,
|
||||
"space_id": pcfCert.SpaceID,
|
||||
},
|
||||
},
|
||||
BoundCIDRs: role.BoundCIDRs,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
config, err := config(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, errors.New("no configuration is available for reaching the PCF API")
|
||||
}
|
||||
|
||||
roleName, err := getOrErr("role", req.Auth.InternalData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role, err := getRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role == nil {
|
||||
return nil, errors.New("no matching role")
|
||||
}
|
||||
|
||||
instanceID, err := getOrErr("instance_id", req.Auth.InternalData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipAddr, err := getOrErr("ip_address", req.Auth.InternalData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgID, err := getOrErr("org_id", req.Auth.Alias.Metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spaceID, err := getOrErr("space_id", req.Auth.Alias.Metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appID, err := getOrErr("app_id", req.Auth.Alias.Metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reconstruct the certificate and ensure it still meets all constraints.
|
||||
pcfCert, err := models.NewPCFCertificate(instanceID, orgID, spaceID, appID, ipAddr)
|
||||
if err := b.validate(config, role, pcfCert, req.Connection.RemoteAddr); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
resp := &logical.Response{Auth: req.Auth}
|
||||
resp.Auth.TTL = role.TTL
|
||||
resp.Auth.MaxTTL = role.MaxTTL
|
||||
resp.Auth.Period = role.Period
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *backend) validate(config *models.Configuration, role *models.RoleEntry, pcfCert *models.PCFCertificate, reqConnRemoteAddr string) error {
|
||||
if !role.DisableIPMatching {
|
||||
if !matchesIPAddress(reqConnRemoteAddr, pcfCert.IPAddress) {
|
||||
return errors.New("no matching IP address")
|
||||
}
|
||||
}
|
||||
if !meetsBoundConstraints(pcfCert.InstanceID, role.BoundInstanceIDs) {
|
||||
return fmt.Errorf("instance ID %s doesn't match role constraints of %s", pcfCert.InstanceID, role.BoundInstanceIDs)
|
||||
}
|
||||
if !meetsBoundConstraints(pcfCert.AppID, role.BoundAppIDs) {
|
||||
return fmt.Errorf("app ID %s doesn't match role constraints of %s", pcfCert.AppID, role.BoundAppIDs)
|
||||
}
|
||||
if !meetsBoundConstraints(pcfCert.OrgID, role.BoundOrgIDs) {
|
||||
return fmt.Errorf("org ID %s doesn't match role constraints of %s", pcfCert.OrgID, role.BoundOrgIDs)
|
||||
}
|
||||
if !meetsBoundConstraints(pcfCert.SpaceID, role.BoundSpaceIDs) {
|
||||
return fmt.Errorf("space ID %s doesn't match role constraints of %s", pcfCert.SpaceID, role.BoundSpaceIDs)
|
||||
}
|
||||
if !cidrutil.RemoteAddrIsOk(reqConnRemoteAddr, role.BoundCIDRs) {
|
||||
return fmt.Errorf("remote address %s doesn't match role constraints of %s", reqConnRemoteAddr, role.BoundCIDRs)
|
||||
}
|
||||
|
||||
// Use the PCF API to ensure everything still exists and to verify whatever we can.
|
||||
client, err := util.NewPCFClient(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Here, if it were possible, we _would_ do an API call to check the instance ID,
|
||||
// but currently there's no known way to do that via the pcf API.
|
||||
|
||||
// Check everything we can using the app ID.
|
||||
app, err := client.AppByGuid(pcfCert.AppID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.Guid != pcfCert.AppID {
|
||||
return fmt.Errorf("cert app ID %s doesn't match API's expected one of %s", pcfCert.AppID, app.Guid)
|
||||
}
|
||||
if app.SpaceGuid != pcfCert.SpaceID {
|
||||
return fmt.Errorf("cert space ID %s doesn't match API's expected one of %s", pcfCert.SpaceID, app.SpaceGuid)
|
||||
}
|
||||
if app.Instances <= 0 {
|
||||
return errors.New("app doesn't have any live instances")
|
||||
}
|
||||
|
||||
// Check everything we can using the org ID.
|
||||
org, err := client.GetOrgByGuid(pcfCert.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if org.Guid != pcfCert.OrgID {
|
||||
return fmt.Errorf("cert org ID %s doesn't match API's expected one of %s", pcfCert.OrgID, org.Guid)
|
||||
}
|
||||
|
||||
// Check everything we can using the space ID.
|
||||
space, err := client.GetSpaceByGuid(pcfCert.SpaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if space.Guid != pcfCert.SpaceID {
|
||||
return fmt.Errorf("cert space ID %s doesn't match API's expected one of %s", pcfCert.SpaceID, space.Guid)
|
||||
}
|
||||
if space.OrganizationGuid != pcfCert.OrgID {
|
||||
return fmt.Errorf("cert org ID %s doesn't match API's expected one of %s", pcfCert.OrgID, space.OrganizationGuid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func meetsBoundConstraints(certValue string, constraints []string) bool {
|
||||
if len(constraints) == 0 {
|
||||
// There are no restrictions, so everything passes this check.
|
||||
return true
|
||||
}
|
||||
// Check whether we have a match.
|
||||
return strutil.StrListContains(constraints, certValue)
|
||||
}
|
||||
|
||||
func matchesIPAddress(remoteAddr string, certIP net.IP) bool {
|
||||
// Some remote addresses may arrive like "10.255.181.105/32"
|
||||
// but the certificate will only have the IP address without
|
||||
// the subnet mask, so that's what we want to match against.
|
||||
// For those wanting to also match the subnet, use bound_cidrs.
|
||||
parts := strings.Split(remoteAddr, "/")
|
||||
reqIPAddr := net.ParseIP(parts[0])
|
||||
if certIP.Equal(reqIPAddr) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Try parsing this as ISO 8601 AND the way that is default provided by Bash to make it easier to give via the CLI as well.
|
||||
func parseTime(signingTime string) (time.Time, error) {
|
||||
if signingTime, err := time.Parse(signatures.TimeFormat, signingTime); err == nil {
|
||||
return signingTime, nil
|
||||
}
|
||||
if signingTime, err := time.Parse(util.BashTimeFormat, signingTime); err == nil {
|
||||
return signingTime, nil
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("couldn't parse %s", signingTime)
|
||||
}
|
||||
|
||||
// getOrErr is a convenience method for pulling a string from a map.
|
||||
func getOrErr(fieldName string, from interface{}) (string, error) {
|
||||
switch givenMap := from.(type) {
|
||||
case map[string]interface{}:
|
||||
vIfc, ok := givenMap[fieldName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unable to retrieve %q during renewal", fieldName)
|
||||
}
|
||||
v, ok := vIfc.(string)
|
||||
if v == "" {
|
||||
return "", fmt.Errorf("unable to retrieve %q during renewal, not a string", fieldName)
|
||||
}
|
||||
return v, nil
|
||||
case map[string]string:
|
||||
v, ok := givenMap[fieldName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unable to retrieve %q during renewal", fieldName)
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unrecognized type for structure containing %s", fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
const pathLoginSyn = `
|
||||
Authenticates an entity with Vault.
|
||||
`
|
||||
|
||||
const pathLoginDesc = `
|
||||
Authenticate PCF entities using a client certificate issued by the
|
||||
configured Certificate Authority, and signed by a client key belonging
|
||||
to the client certificate.
|
||||
`
|
|
@ -0,0 +1,275 @@
|
|||
package pcf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/models"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/parseutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
const roleStoragePrefix = "roles/"
|
||||
|
||||
func (b *backend) pathListRoles() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "roles/?$",
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ListOperation: &framework.PathOperation{
|
||||
Callback: b.operationRolesList,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: pathListRolesHelpSyn,
|
||||
HelpDescription: pathListRolesHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) operationRolesList(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
entries, err := req.Storage.List(ctx, roleStoragePrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return logical.ListResponse(entries), nil
|
||||
}
|
||||
|
||||
func (b *backend) pathRoles() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "roles/" + framework.GenericNameRegex("role"),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"role": {
|
||||
Type: framework.TypeLowerCaseString,
|
||||
Required: true,
|
||||
Description: "The name of the role.",
|
||||
},
|
||||
"bound_application_ids": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
DisplayName: "Bound Application IDs",
|
||||
DisplayValue: "6b814521-5f08-4b1a-8c4e-fbe7c5f3a169",
|
||||
Description: "Require that the client certificate presented has at least one of these app IDs.",
|
||||
},
|
||||
"bound_space_ids": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
DisplayName: "Bound Space IDs",
|
||||
DisplayValue: "3d2eba6b-ef19-44d5-91dd-1975b0db5cc9",
|
||||
Description: "Require that the client certificate presented has at least one of these space IDs.",
|
||||
},
|
||||
"bound_organization_ids": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
DisplayName: "Bound Organization IDs",
|
||||
DisplayValue: "34a878d0-c2f9-4521-ba73-a9f664e82c7b",
|
||||
Description: "Require that the client certificate presented has at least one of these org IDs.",
|
||||
},
|
||||
"bound_instance_ids": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
DisplayName: "Bound Instance IDs",
|
||||
DisplayValue: "8a886b31-ccf7-480d-54d8-cc28",
|
||||
Description: "Require that the client certificate presented has at least one of these instance IDs.",
|
||||
},
|
||||
"bound_cidrs": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
DisplayName: "Bound CIDRs",
|
||||
DisplayValue: "192.168.100.14/24",
|
||||
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
||||
IP addresses which can perform the login operation.`,
|
||||
},
|
||||
"policies": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Default: "default",
|
||||
DisplayName: "Policies",
|
||||
DisplayValue: "default",
|
||||
Description: "Comma separated list of policies on the role.",
|
||||
},
|
||||
"disable_ip_matching": {
|
||||
Type: framework.TypeBool,
|
||||
Default: false,
|
||||
DisplayName: "Disable IP Address Matching",
|
||||
DisplayValue: "false",
|
||||
Description: `If set to true, disables the default behavior that logging in must be performed from
|
||||
an acceptable IP address described by the certificate presented.`,
|
||||
},
|
||||
"ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: `Duration in seconds after which the issued token should expire. Defaults
|
||||
to 0, in which case the value will fallback to the system/mount defaults.`,
|
||||
},
|
||||
"max_ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: "The maximum allowed lifetime of tokens issued using this role.",
|
||||
},
|
||||
"period": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: 0,
|
||||
DisplayName: "Period",
|
||||
Description: `If set, indicates that the token generated using this role
|
||||
should never expire. The token should be renewed within the
|
||||
duration specified by this value. At each renewal, the token's
|
||||
TTL will be set to the value of this parameter.`,
|
||||
},
|
||||
},
|
||||
ExistenceCheck: b.operationRolesExistenceCheck,
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.CreateOperation: &framework.PathOperation{
|
||||
Callback: b.operationRolesCreateUpdate,
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.operationRolesCreateUpdate,
|
||||
},
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
Callback: b.operationRolesRead,
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
Callback: b.operationRolesDelete,
|
||||
},
|
||||
},
|
||||
HelpSynopsis: pathRolesHelpSyn,
|
||||
HelpDescription: pathRolesHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) operationRolesExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
|
||||
entry, err := req.Storage.Get(ctx, roleStoragePrefix+data.Get("role").(string))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return entry != nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) operationRolesCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("role").(string)
|
||||
|
||||
role := &models.RoleEntry{}
|
||||
if req.Operation == logical.UpdateOperation {
|
||||
storedRole, err := getRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if storedRole != nil {
|
||||
role = storedRole
|
||||
}
|
||||
}
|
||||
if raw, ok := data.GetOk("bound_application_ids"); ok {
|
||||
role.BoundAppIDs = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("bound_space_ids"); ok {
|
||||
role.BoundSpaceIDs = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("bound_organization_ids"); ok {
|
||||
role.BoundOrgIDs = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("bound_instance_ids"); ok {
|
||||
role.BoundInstanceIDs = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("bound_cidrs"); ok {
|
||||
parsedCIDRs, err := parseutil.ParseAddrs(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
role.BoundCIDRs = parsedCIDRs
|
||||
}
|
||||
if raw, ok := data.GetOk("policies"); ok {
|
||||
role.Policies = raw.([]string)
|
||||
}
|
||||
if raw, ok := data.GetOk("disable_ip_matching"); ok {
|
||||
role.DisableIPMatching = raw.(bool)
|
||||
}
|
||||
if raw, ok := data.GetOk("ttl"); ok {
|
||||
role.TTL = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
if raw, ok := data.GetOk("max_ttl"); ok {
|
||||
role.MaxTTL = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
if raw, ok := data.GetOk("period"); ok {
|
||||
role.Period = time.Duration(raw.(int)) * time.Second
|
||||
}
|
||||
|
||||
if role.MaxTTL > 0 && role.TTL > role.MaxTTL {
|
||||
return logical.ErrorResponse("ttl exceeds max_ttl"), nil
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON(roleStoragePrefix+roleName, role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if role.TTL > b.System().MaxLeaseTTL() {
|
||||
resp := &logical.Response{}
|
||||
resp.AddWarning(fmt.Sprintf("ttl of %d exceeds the system max ttl of %d, the latter will be used during login", role.TTL, b.System().MaxLeaseTTL()))
|
||||
return resp, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) operationRolesRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("role").(string)
|
||||
role, err := getRole(ctx, req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role == nil {
|
||||
return nil, nil
|
||||
}
|
||||
cidrs := make([]string, len(role.BoundCIDRs))
|
||||
for i, cidr := range role.BoundCIDRs {
|
||||
cidrs[i] = cidr.String()
|
||||
}
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"bound_application_ids": role.BoundAppIDs,
|
||||
"bound_space_ids": role.BoundSpaceIDs,
|
||||
"bound_organization_ids": role.BoundOrgIDs,
|
||||
"bound_instance_ids": role.BoundInstanceIDs,
|
||||
"bound_cidrs": cidrs,
|
||||
"policies": role.Policies,
|
||||
"disable_ip_matching": role.DisableIPMatching,
|
||||
"ttl": role.TTL / time.Second,
|
||||
"max_ttl": role.MaxTTL / time.Second,
|
||||
"period": role.Period / time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) operationRolesDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("role").(string)
|
||||
if err := req.Storage.Delete(ctx, roleStoragePrefix+roleName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getRole(ctx context.Context, storage logical.Storage, roleName string) (*models.RoleEntry, error) {
|
||||
r := &models.RoleEntry{}
|
||||
entry, err := storage.Get(ctx, roleStoragePrefix+roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if err := entry.DecodeJSON(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
const pathListRolesHelpSyn = "List the existing roles in this backend."
|
||||
|
||||
const pathListRolesHelpDesc = "Roles will be listed by the role name."
|
||||
|
||||
const pathRolesHelpSyn = `
|
||||
Read, write and reference policies and roles that tokens can be made for.
|
||||
`
|
||||
|
||||
const pathRolesHelpDesc = `
|
||||
This path allows you to read and write roles that are used to
|
||||
create Vault tokens.
|
||||
Once configured, credentials will be able to be obtained using this role name
|
||||
if the caller can successfully provide a client certificate, and sign it
|
||||
using a valid secret key. The client certificate provided must have been issued
|
||||
by the configured certficate authority. Its parameters must also match anything
|
||||
you've listed as "bound".
|
||||
`
|
118
vendor/github.com/hashicorp/vault-plugin-auth-pcf/signatures/version1.go
generated
vendored
Normal file
118
vendor/github.com/hashicorp/vault-plugin-auth-pcf/signatures/version1.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
|||
package signatures
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
const TimeFormat = "2006-01-02T15:04:05Z"
|
||||
|
||||
type SignatureData struct {
|
||||
SigningTime time.Time
|
||||
Role string
|
||||
|
||||
// CFInstanceCertContents are the full contents/body of the file
|
||||
// available at CF_INSTANCE_CERT. When viewed visually, this file
|
||||
// will contain two certificates. Generally, the first one is the
|
||||
// identity certificate itself, and the second one is the intermediate
|
||||
// certificate that issued it.
|
||||
CFInstanceCertContents string
|
||||
}
|
||||
|
||||
func (s *SignatureData) hash() []byte {
|
||||
sum := sha256.Sum256([]byte(s.toSign()))
|
||||
return sum[:]
|
||||
}
|
||||
|
||||
func (s *SignatureData) toSign() string {
|
||||
toHash := ""
|
||||
for _, field := range []string{s.SigningTime.UTC().Format(TimeFormat), s.CFInstanceCertContents, s.Role} {
|
||||
toHash += field
|
||||
}
|
||||
return toHash
|
||||
}
|
||||
|
||||
func Sign(pathToPrivateKey string, signatureData *SignatureData) (string, error) {
|
||||
if signatureData == nil {
|
||||
return "", errors.New("signatureData must be provided")
|
||||
}
|
||||
|
||||
keyBytes, err := ioutil.ReadFile(pathToPrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
block, _ := pem.Decode(keyBytes)
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("unable to decode RSA private key from %s", keyBytes)
|
||||
}
|
||||
rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signatureBytes, err := rsa.SignPSS(rand.Reader, rsaPrivateKey, crypto.SHA256, signatureData.hash(), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(signatureBytes), nil
|
||||
}
|
||||
|
||||
// Verify ensures that a given signature was created by a private key
|
||||
// matching one of the given instance certificates. It returns the matching
|
||||
// certificate, which should further be verified to be the identity certificate,
|
||||
// and to be issued by a chain leading to the root CA certificate. There's a
|
||||
// util function for this named Validate.
|
||||
func Verify(signature string, signatureData *SignatureData) (*x509.Certificate, error) {
|
||||
if signatureData == nil {
|
||||
return nil, errors.New("signatureData must be provided")
|
||||
}
|
||||
|
||||
// Use the CA certificate to verify the signature we've received.
|
||||
signatureBytes, err := base64.URLEncoding.DecodeString(signature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfInstanceCertContentsBytes := []byte(signatureData.CFInstanceCertContents)
|
||||
var block *pem.Block
|
||||
var result error
|
||||
for {
|
||||
block, cfInstanceCertContentsBytes = pem.Decode(cfInstanceCertContentsBytes)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
instanceCerts, err := x509.ParseCertificates(block.Bytes)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
for _, instanceCert := range instanceCerts {
|
||||
publicKey, ok := instanceCert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
result = multierror.Append(result, fmt.Errorf("not an rsa public key, it's a %t", instanceCert.PublicKey))
|
||||
continue
|
||||
}
|
||||
if err := rsa.VerifyPSS(publicKey, crypto.SHA256, signatureData.hash(), signatureBytes, nil); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
// Success
|
||||
return instanceCert, nil
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("no matching certificate found for %s in %s", signature, signatureData.CFInstanceCertContents)
|
||||
}
|
||||
return nil, result
|
||||
}
|
282
vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go
generated
vendored
Normal file
282
vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go
generated
vendored
Normal file
|
@ -0,0 +1,282 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
)
|
||||
|
||||
// Generate is a convenience method for testing. It creates a group of test certificates with the
|
||||
// client certificate reflecting the given values. Close() should be called when done to immediately
|
||||
// delete the three temporary files it has created.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// testCerts, err := certificates.Generate(...)
|
||||
// if err != nil {
|
||||
// ...
|
||||
// }
|
||||
// defer func(){
|
||||
// if err := testCerts.Close(); err != nil {
|
||||
// ...
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
func Generate(instanceID, orgID, spaceID, appID, ipAddress string) (*TestCertificates, error) {
|
||||
caCert, instanceCert, instanceKey, err := generate(instanceID, orgID, spaceID, appID, ipAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Keep a list of paths we've created so that if we fail along the way,
|
||||
// we can attempt to clean them up.
|
||||
var paths []string
|
||||
pathToCACertificate, err := makePathTo(caCert)
|
||||
if err != nil {
|
||||
// No path was successfully created, so we don't need to cleanup here.
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, pathToCACertificate)
|
||||
|
||||
pathToInstanceCertificate, err := makePathTo(instanceCert)
|
||||
if err != nil {
|
||||
if cleanupErr := cleanup(paths); cleanupErr != nil {
|
||||
return nil, multierror.Append(err, cleanupErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, pathToInstanceCertificate)
|
||||
|
||||
pathToInstanceKey, err := makePathTo(instanceKey)
|
||||
if err != nil {
|
||||
if cleanupErr := cleanup(paths); cleanupErr != nil {
|
||||
return nil, multierror.Append(err, cleanupErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, pathToInstanceKey)
|
||||
|
||||
// Provide a function to be called at the end cleaning up our temporary files.
|
||||
cleanup := func() error {
|
||||
return cleanup(paths)
|
||||
}
|
||||
|
||||
return &TestCertificates{
|
||||
CACertificate: caCert,
|
||||
InstanceCertificate: instanceCert,
|
||||
InstanceKey: instanceKey,
|
||||
PathToCACertificate: pathToCACertificate,
|
||||
PathToInstanceCertificate: pathToInstanceCertificate,
|
||||
PathToInstanceKey: pathToInstanceKey,
|
||||
cleanup: cleanup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type TestCertificates struct {
|
||||
CACertificate string
|
||||
InstanceCertificate string
|
||||
InstanceKey string
|
||||
|
||||
PathToCACertificate string
|
||||
PathToInstanceCertificate string
|
||||
PathToInstanceKey string
|
||||
|
||||
// cleanup contains a function that has a path to all the temporary files we made,
|
||||
// and deletes them. They're all in the /tmp folder so they'll disappear on the next
|
||||
// system restart anyways, but in case of repeated tests, it's best to leave nothing
|
||||
// behind if possible.
|
||||
cleanup func() error
|
||||
}
|
||||
|
||||
func (e *TestCertificates) Close() error {
|
||||
return e.cleanup()
|
||||
}
|
||||
|
||||
func generate(instanceID, orgID, spaceID, appID, ipAddress string) (caCert, instanceCert, instanceKey string, err error) {
|
||||
caCert, caPriv, err := generateCA("", nil)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
intermediateCert, intermediatePriv, err := generateCA(caCert, caPriv)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
identityCert, identityPriv, err := generateIdentity(intermediateCert, intermediatePriv, instanceID, orgID, spaceID, appID, ipAddress)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
// Convert the identity key to something appropriate for a file body.
|
||||
out := &bytes.Buffer{}
|
||||
pem.Encode(out, pemBlockForKey(identityPriv))
|
||||
instanceKey = out.String()
|
||||
return caCert, fmt.Sprintf("%s%s", intermediateCert, identityCert), instanceKey, nil
|
||||
}
|
||||
|
||||
func generateCA(caCert string, caPriv *rsa.PrivateKey) (string, *rsa.PrivateKey, error) {
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Country: []string{"US"},
|
||||
Province: []string{"CA"},
|
||||
Organization: []string{"Testing, Inc."},
|
||||
CommonName: "test-CA",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
// Default to self-signing certificates by listing using itself as a parent.
|
||||
parent := &template
|
||||
|
||||
// If a cert is provided, use it as the parent.
|
||||
if caCert != "" {
|
||||
block, certBytes := pem.Decode([]byte(caCert))
|
||||
if block == nil {
|
||||
return "", nil, errors.New("block shouldn't be nil")
|
||||
}
|
||||
if len(certBytes) > 0 {
|
||||
return "", nil, errors.New("there shouldn't be more bytes")
|
||||
}
|
||||
ca509cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
parent = ca509cert
|
||||
}
|
||||
// If a private key isn't provided, make a new one.
|
||||
priv := caPriv
|
||||
if priv == nil {
|
||||
newPriv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
priv = newPriv
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent, publicKey(priv), priv)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
cert := out.String()
|
||||
return cert, priv, nil
|
||||
}
|
||||
|
||||
func generateIdentity(caCert string, caPriv *rsa.PrivateKey, instanceID, orgID, spaceID, appID, ipAddress string) (string, *rsa.PrivateKey, error) {
|
||||
block, certBytes := pem.Decode([]byte(caCert))
|
||||
if block == nil {
|
||||
return "", nil, errors.New("block shouldn't be nil")
|
||||
}
|
||||
if len(certBytes) > 0 {
|
||||
return "", nil, errors.New("there shouldn't be more bytes")
|
||||
}
|
||||
ca509cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Country: []string{"US"},
|
||||
Province: []string{"CA"},
|
||||
Organization: []string{"Cloud Foundry"},
|
||||
OrganizationalUnit: []string{
|
||||
fmt.Sprintf("organization:%s", orgID),
|
||||
fmt.Sprintf("space:%s", spaceID),
|
||||
fmt.Sprintf("app:%s", appID),
|
||||
},
|
||||
CommonName: instanceID,
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
IPAddresses: []net.IP{net.ParseIP(ipAddress)},
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, ca509cert, publicKey(priv), caPriv)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
cert := out.String()
|
||||
return cert, priv, nil
|
||||
}
|
||||
|
||||
func makePathTo(certOrKey string) (string, error) {
|
||||
u, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFile, err := ioutil.TempFile("", u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := tmpFile.Write([]byte(certOrKey)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tmpFile.Name(), nil
|
||||
}
|
||||
|
||||
func publicKey(priv interface{}) interface{} {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func pemBlockForKey(priv interface{}) *pem.Block {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup(paths []string) error {
|
||||
var result error
|
||||
for i := 0; i < len(paths); i++ {
|
||||
if err := os.Remove(paths[i]); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
292
vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf/mock.go
generated
vendored
Normal file
292
vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf/mock.go
generated
vendored
Normal file
|
@ -0,0 +1,292 @@
|
|||
package pcf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
)
|
||||
|
||||
const (
|
||||
AuthUsername = "username"
|
||||
AuthPassword = "password"
|
||||
|
||||
FoundServiceGUID = "1bf2e7f6-2d1d-41ec-501c-c70"
|
||||
FoundAppGUID = "2d3e834a-3a25-4591-974c-fa5626d5d0a1"
|
||||
FoundOrgGUID = "34a878d0-c2f9-4521-ba73-a9f664e82c7bf"
|
||||
FoundSpaceGUID = "3d2eba6b-ef19-44d5-91dd-1975b0db5cc9"
|
||||
|
||||
UnfoundServiceGUID = "service-id-unfound"
|
||||
UnfoundAppGUID = "app-id-unfound"
|
||||
UnfoundOrgID = "org-id-unfound"
|
||||
UnfoundSpaceGUID = "space-id-unfound"
|
||||
)
|
||||
|
||||
var (
|
||||
testServerUrl = ""
|
||||
logger = hclog.Default()
|
||||
)
|
||||
|
||||
func MockServer(loud bool) *httptest.Server {
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if loud {
|
||||
logger.Info(fmt.Sprintf("%+v", r))
|
||||
}
|
||||
|
||||
// Below, 200's are returned by default, but are included anyways for explicitness.
|
||||
pathFields := strings.Split(r.URL.EscapedPath(), "/")
|
||||
lastPathField := pathFields[len(pathFields)-1]
|
||||
switch lastPathField {
|
||||
case "token":
|
||||
w.Header().Add("Content-Type", "application/json;charset=UTF-8")
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(tokenResponse))
|
||||
|
||||
case "info":
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(strings.Replace(infoResponse, "{{TEST_URL}}", testServerUrl, -1)))
|
||||
|
||||
case FoundServiceGUID:
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(serviceInstanceResponse))
|
||||
|
||||
case UnfoundServiceGUID:
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(unfoundServiceInstanceResponse))
|
||||
|
||||
case FoundAppGUID:
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(appResponse))
|
||||
|
||||
case UnfoundAppGUID:
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(unfoundAppResponse))
|
||||
|
||||
case FoundOrgGUID:
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(orgResponse))
|
||||
|
||||
case UnfoundOrgID:
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(unfoundOrgResponse))
|
||||
|
||||
case FoundSpaceGUID:
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(spaceResponse))
|
||||
|
||||
case UnfoundSpaceGUID:
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(unfoundSpaceResponse))
|
||||
|
||||
default:
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte(fmt.Sprintf("unexpected object identifier: %s", lastPathField)))
|
||||
}
|
||||
}))
|
||||
testServerUrl = testServer.URL
|
||||
return testServer
|
||||
}
|
||||
|
||||
const (
|
||||
tokenResponse = `{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vdWFhLmRldi5jZmRldi5zaC90b2tlbl9rZXlzIiwia2lkIjoia2V5LTEiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIxM2NiMzAyYjFjNjY0MDdkOWY3MDM2YzJjMmUxZDEyMCIsInN1YiI6IjYxMWM3ZWVhLWZmZDAtNGU5OC04MmYwLWY0YjU0YWZmNmRjYiIsInNjb3BlIjpbImNsaWVudHMucmVhZCIsIm9wZW5pZCIsInJvdXRpbmcucm91dGVyX2dyb3Vwcy53cml0ZSIsInNjaW0ucmVhZCIsImNsb3VkX2NvbnRyb2xsZXIuYWRtaW4iLCJ1YWEudXNlciIsInJvdXRpbmcucm91dGVyX2dyb3Vwcy5yZWFkIiwiY2xvdWRfY29udHJvbGxlci5yZWFkIiwicGFzc3dvcmQud3JpdGUiLCJjbG91ZF9jb250cm9sbGVyLndyaXRlIiwibmV0d29yay5hZG1pbiIsImRvcHBsZXIuZmlyZWhvc2UiLCJzY2ltLndyaXRlIl0sImNsaWVudF9pZCI6ImNmIiwiY2lkIjoiY2YiLCJhenAiOiJjZiIsImdyYW50X3R5cGUiOiJwYXNzd29yZCIsInVzZXJfaWQiOiI2MTFjN2VlYS1mZmQwLTRlOTgtODJmMC1mNGI1NGFmZjZkY2IiLCJvcmlnaW4iOiJ1YWEiLCJ1c2VyX25hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW4iLCJhdXRoX3RpbWUiOjE1NTgzNzUwODksInJldl9zaWciOiIxOTA1YTEzOSIsImlhdCI6MTU1ODM3NTA4OSwiZXhwIjoxNTU4Mzc1Njg5LCJpc3MiOiJodHRwczovL3VhYS5kZXYuY2ZkZXYuc2gvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsic2NpbSIsImNsb3VkX2NvbnRyb2xsZXIiLCJwYXNzd29yZCIsImNmIiwiY2xpZW50cyIsInVhYSIsIm9wZW5pZCIsImRvcHBsZXIiLCJyb3V0aW5nLnJvdXRlcl9ncm91cHMiLCJuZXR3b3JrIl19.KSdNhoQSTCh_3zJPLvxeAhEyAfVTvHN1mKprHqfDJJ79WaaEsUM-mLO68QWPvBgON5dx8dOE8GaQw--xpqpqNwncb7MN8jmz_lZxgw-6oOf_O-bYJmGsaxX-ETlMLKvuqUljSC5KvB16zBkRtAP2IhQsMOV-PGdx2Lz4CqBkzALHL4MUlnaaI6Z1O-zMVhFFunpmY-mYZqaHNw_35cNohieehq1TrrqVdHCiNkNVYi7LQPS93Ow8VC6I3GFNzNr6EAjmHu9tEq3sTKAfsBg8zEWjB_25cpiWW5gL-dPhZd4KSgp3wOh1K4kpWw7NKpLnPxf7mcRH4IgNDZPJqkqAjA",
|
||||
"token_type": "bearer",
|
||||
"id_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vdWFhLmRldi5jZmRldi5zaC90b2tlbl9rZXlzIiwia2lkIjoia2V5LTEiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiI2MTFjN2VlYS1mZmQwLTRlOTgtODJmMC1mNGI1NGFmZjZkY2IiLCJhdWQiOlsiY2YiXSwiaXNzIjoiaHR0cHM6Ly91YWEuZGV2LmNmZGV2LnNoL29hdXRoL3Rva2VuIiwiZXhwIjoxNTU4Mzc1Njg5LCJpYXQiOjE1NTgzNzUwODksImFtciI6WyJwd2QiXSwiYXpwIjoiY2YiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJhZG1pbiIsInppZCI6InVhYSIsIm9yaWdpbiI6InVhYSIsImp0aSI6IjEzY2IzMDJiMWM2NjQwN2Q5ZjcwMzZjMmMyZTFkMTIwIiwicHJldmlvdXNfbG9nb25fdGltZSI6MTU1ODM3NDk0NTEyMCwiZW1haWxfdmVyaWZpZWQiOnRydWUsImNsaWVudF9pZCI6ImNmIiwiY2lkIjoiY2YiLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX25hbWUiOiJhZG1pbiIsInJldl9zaWciOiIxOTA1YTEzOSIsInVzZXJfaWQiOiI2MTFjN2VlYS1mZmQwLTRlOTgtODJmMC1mNGI1NGFmZjZkY2IiLCJhdXRoX3RpbWUiOjE1NTgzNzUwODl9.eOv9O17i1naYiycCwlXFu2Xh2xjBRNBagq61AX1y2Upb7ek42VFaAi92PAZN9rmcU9i3trvERen0Hv7aIottLM7U-MTKMBnHXjqr1fY5oWyWxGruWsM0T9RBu4g9dbs8hyqIh_be9KdiL4PSybChV7-RspF1kMa58OUvpgQbQhgOMMWKKODYVXeeY8z241octX_ST-5tZv_josk12sworPQbZCwA5QbUjmCNSc_fHg9xe4Ra_Wecq3hmmspHrHW8gTc6ggoWUzxbbCKo1rF2PIVHzJ_61cLaHBepax9DvhCYnSJtDjlG5lPy41dxc01dOAD-JLEaV-CigtrWntFUXQ",
|
||||
"refresh_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vdWFhLmRldi5jZmRldi5zaC90b2tlbl9rZXlzIiwia2lkIjoia2V5LTEiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIwOTRlYWQ0ZThiYWM0Nzk1ODJmMDI2ZmMwMjUwNTA2Yy1yIiwic3ViIjoiNjExYzdlZWEtZmZkMC00ZTk4LTgyZjAtZjRiNTRhZmY2ZGNiIiwiaWF0IjoxNTU4Mzc1MDg5LCJleHAiOjE1NjA5NjcwODksImNpZCI6ImNmIiwiY2xpZW50X2lkIjoiY2YiLCJpc3MiOiJodHRwczovL3VhYS5kZXYuY2ZkZXYuc2gvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsic2NpbSIsImNsb3VkX2NvbnRyb2xsZXIiLCJwYXNzd29yZCIsImNmIiwiY2xpZW50cyIsInVhYSIsIm9wZW5pZCIsImRvcHBsZXIiLCJyb3V0aW5nLnJvdXRlcl9ncm91cHMiLCJuZXR3b3JrIl0sImdyYW50ZWRfc2NvcGVzIjpbImNsaWVudHMucmVhZCIsIm9wZW5pZCIsInJvdXRpbmcucm91dGVyX2dyb3Vwcy53cml0ZSIsInNjaW0ucmVhZCIsImNsb3VkX2NvbnRyb2xsZXIuYWRtaW4iLCJ1YWEudXNlciIsInJvdXRpbmcucm91dGVyX2dyb3Vwcy5yZWFkIiwiY2xvdWRfY29udHJvbGxlci5yZWFkIiwicGFzc3dvcmQud3JpdGUiLCJjbG91ZF9jb250cm9sbGVyLndyaXRlIiwibmV0d29yay5hZG1pbiIsImRvcHBsZXIuZmlyZWhvc2UiLCJzY2ltLndyaXRlIl0sImFtciI6WyJwd2QiXSwiYXV0aF90aW1lIjoxNTU4Mzc1MDg5LCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX25hbWUiOiJhZG1pbiIsIm9yaWdpbiI6InVhYSIsInVzZXJfaWQiOiI2MTFjN2VlYS1mZmQwLTRlOTgtODJmMC1mNGI1NGFmZjZkY2IiLCJyZXZfc2lnIjoiMTkwNWExMzkifQ.LFkoBtAWGL1x1bUo0ak16f-NeWpBS6NZspVwzaVhBv4xg7qxDryUayE5M2BQOGMb4tZLOU2cYyO2uu4li70u0LgJk7k3OZ0-hxKvjX4sJcoiLlJFCEsFzq_yG6iUFnA2w2kA70IQtACvAAHO--Jz0L1QGA8ebt20z7Rup0FufyDJFFevhbppzYb6AfghhnrB-yZbZU9rPq4Q8DWDTN0nMOBn05CA52NRKoj2157JXLRimEG7SZW6dhXUhdjbCvSz1WKiG6fS3fHK5ncqyQtuSqLfI0Naq1v77wfSzbvc0MB-IM4CPYc-ODhWbHFoV1z8kV6dWXm2ng7OyZe3u3A7Fw",
|
||||
"expires_in": 599,
|
||||
"scope": "clients.read openid routing.router_groups.write scim.read cloud_controller.admin uaa.user routing.router_groups.read cloud_controller.read password.write cloud_controller.write network.admin doppler.firehose scim.write",
|
||||
"jti": "13cb302b1c66407d9f7036c2c2e1d120"
|
||||
}`
|
||||
|
||||
infoResponse = `{
|
||||
"name": "",
|
||||
"build": "",
|
||||
"support": "",
|
||||
"version": 0,
|
||||
"description": "",
|
||||
"authorization_endpoint": "https://login.dev.cfdev.sh",
|
||||
"token_endpoint": "{{TEST_URL}}",
|
||||
"min_cli_version": null,
|
||||
"min_recommended_cli_version": null,
|
||||
"app_ssh_endpoint": "ssh.dev.cfdev.sh:2222",
|
||||
"app_ssh_host_key_fingerprint": "96:4d:89:2d:39:18:bc:16:e1:d3:d8:44:f8:16:af:85",
|
||||
"app_ssh_oauth_client": "ssh-proxy",
|
||||
"doppler_logging_endpoint": "wss://doppler.dev.cfdev.sh:443",
|
||||
"api_version": "2.133.0",
|
||||
"osbapi_version": "2.14",
|
||||
"routing_endpoint": "https://api.dev.cfdev.sh/routing",
|
||||
"user": "611c7eea-ffd0-4e98-82f0-f4b54aff6dcb"
|
||||
}`
|
||||
|
||||
serviceInstanceResponse = `{
|
||||
"metadata": {
|
||||
"guid": "1bf2e7f6-2d1d-41ec-501c-c70",
|
||||
"url": "/v2/service_instances/1bf2e7f6-2d1d-41ec-501c-c70",
|
||||
"created_at": "2016-06-08T16:41:29Z",
|
||||
"updated_at": "2016-06-08T16:41:26Z"
|
||||
},
|
||||
"entity": {
|
||||
"name": "name-1508",
|
||||
"credentials": {
|
||||
"creds-key-38": "creds-val-38"
|
||||
},
|
||||
"service_guid": "a14baddf-1ccc-5299-0152-ab9s49de4422",
|
||||
"service_plan_guid": "779d2df0-9cdd-48e8-9781-ea05301cedb1",
|
||||
"space_guid": "3d2eba6b-ef19-44d5-91dd-1975b0db5cc9",
|
||||
"gateway_data": null,
|
||||
"dashboard_url": null,
|
||||
"type": "managed_service_instance",
|
||||
"last_operation": {
|
||||
"type": "create",
|
||||
"state": "succeeded",
|
||||
"description": "service broker-provided description",
|
||||
"updated_at": "2016-06-08T16:41:29Z",
|
||||
"created_at": "2016-06-08T16:41:29Z"
|
||||
},
|
||||
"tags": [
|
||||
"accounting",
|
||||
"mongodb"
|
||||
],
|
||||
"space_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9",
|
||||
"service_url": "/v2/services/a14baddf-1ccc-5299-0152-ab9s49de4422",
|
||||
"service_plan_url": "/v2/service_plans/779d2df0-9cdd-48e8-9781-ea05301cedb1",
|
||||
"service_bindings_url": "/v2/service_instances/1bf2e7f6-2d1d-41ec-501c-c70/service_bindings",
|
||||
"service_keys_url": "/v2/service_instances/1bf2e7f6-2d1d-41ec-501c-c70/service_keys",
|
||||
"routes_url": "/v2/service_instances/1bf2e7f6-2d1d-41ec-501c-c70/routes",
|
||||
"shared_from_url": "/v2/service_instances/1bf2e7f6-2d1d-41ec-501c-c70/shared_from",
|
||||
"shared_to_url": "/v2/service_instances/1bf2e7f6-2d1d-41ec-501c-c70/shared_to",
|
||||
"service_instance_parameters_url": "/v2/service_instances/1bf2e7f6-2d1d-41ec-501c-c70/parameters"
|
||||
}
|
||||
}`
|
||||
|
||||
unfoundServiceInstanceResponse = `{
|
||||
"description": "The service instance could not be found: service-id-unfound",
|
||||
"error_code": "CF-ServiceInstanceNotFound",
|
||||
"code": 60004
|
||||
}`
|
||||
|
||||
appResponse = `{
|
||||
"metadata": {
|
||||
"guid": "2d3e834a-3a25-4591-974c-fa5626d5d0a1",
|
||||
"url": "/v2/apps/2d3e834a-3a25-4591-974c-fa5626d5d0a1",
|
||||
"created_at": "2016-06-08T16:41:44Z",
|
||||
"updated_at": "2016-06-08T16:41:44Z"
|
||||
},
|
||||
"entity": {
|
||||
"name": "name-2401",
|
||||
"production": false,
|
||||
"space_guid": "3d2eba6b-ef19-44d5-91dd-1975b0db5cc9",
|
||||
"stack_guid": "7e03186d-a438-4285-b3b7-c426532e1df2",
|
||||
"buildpack": null,
|
||||
"detected_buildpack": null,
|
||||
"detected_buildpack_guid": null,
|
||||
"environment_json": null,
|
||||
"memory": 1024,
|
||||
"instances": 1,
|
||||
"disk_quota": 1024,
|
||||
"state": "STOPPED",
|
||||
"version": "df19a7ea-2003-4ecb-a909-e630e43f2719",
|
||||
"command": null,
|
||||
"console": false,
|
||||
"debug": null,
|
||||
"staging_task_id": null,
|
||||
"package_state": "PENDING",
|
||||
"health_check_http_endpoint": "",
|
||||
"health_check_type": "port",
|
||||
"health_check_timeout": null,
|
||||
"staging_failed_reason": null,
|
||||
"staging_failed_description": null,
|
||||
"diego": false,
|
||||
"docker_image": null,
|
||||
"docker_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"package_updated_at": "2016-06-08T16:41:45Z",
|
||||
"detected_start_command": "",
|
||||
"enable_ssh": true,
|
||||
"ports": null,
|
||||
"space_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9",
|
||||
"stack_url": "/v2/stacks/7e03186d-a438-4285-b3b7-c426532e1df2",
|
||||
"routes_url": "/v2/apps/2d3e834a-3a25-4591-974c-fa5626d5d0a1/routes",
|
||||
"events_url": "/v2/apps/2d3e834a-3a25-4591-974c-fa5626d5d0a1/events",
|
||||
"service_bindings_url": "/v2/apps/2d3e834a-3a25-4591-974c-fa5626d5d0a1/service_bindings",
|
||||
"route_mappings_url": "/v2/apps/2d3e834a-3a25-4591-974c-fa5626d5d0a1/route_mappings"
|
||||
}
|
||||
}`
|
||||
|
||||
unfoundAppResponse = `{
|
||||
"description": "The app could not be found: app-id-unfound",
|
||||
"error_code": "CF-AppNotFound",
|
||||
"code": 100004
|
||||
}`
|
||||
|
||||
orgResponse = `{
|
||||
"metadata": {
|
||||
"guid": "34a878d0-c2f9-4521-ba73-a9f664e82c7bf",
|
||||
"url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf",
|
||||
"created_at": "2019-05-17T22:49:40Z",
|
||||
"updated_at": "2019-05-17T22:49:40Z"
|
||||
},
|
||||
"entity": {
|
||||
"name": "system",
|
||||
"billing_enabled": false,
|
||||
"quota_definition_guid": "b172ff20-ae6d-4a13-a554-dc22f3844fb0",
|
||||
"status": "active",
|
||||
"default_isolation_segment_guid": null,
|
||||
"quota_definition_url": "/v2/quota_definitions/b172ff20-ae6d-4a13-a554-dc22f3844fb0",
|
||||
"spaces_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/spaces",
|
||||
"domains_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/domains",
|
||||
"private_domains_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/private_domains",
|
||||
"users_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/users",
|
||||
"managers_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/managers",
|
||||
"billing_managers_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/billing_managers",
|
||||
"auditors_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/auditors",
|
||||
"app_events_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/app_events",
|
||||
"space_quota_definitions_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf/space_quota_definitions"
|
||||
}
|
||||
}`
|
||||
|
||||
unfoundOrgResponse = `{
|
||||
"description": "The organization could not be found: org-id-unfound",
|
||||
"error_code": "CF-OrganizationNotFound",
|
||||
"code": 30003
|
||||
}`
|
||||
|
||||
spaceResponse = `{
|
||||
"metadata": {
|
||||
"guid": "3d2eba6b-ef19-44d5-91dd-1975b0db5cc9",
|
||||
"url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9",
|
||||
"created_at": "2019-05-17T22:53:30Z",
|
||||
"updated_at": "2019-05-17T22:53:30Z"
|
||||
},
|
||||
"entity": {
|
||||
"name": "cfdev-space",
|
||||
"organization_guid": "34a878d0-c2f9-4521-ba73-a9f664e82c7bf",
|
||||
"space_quota_definition_guid": null,
|
||||
"isolation_segment_guid": null,
|
||||
"allow_ssh": true,
|
||||
"organization_url": "/v2/organizations/34a878d0-c2f9-4521-ba73-a9f664e82c7bf",
|
||||
"developers_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/developers",
|
||||
"managers_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/managers",
|
||||
"auditors_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/auditors",
|
||||
"apps_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/apps",
|
||||
"routes_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/routes",
|
||||
"domains_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/domains",
|
||||
"service_instances_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/service_instances",
|
||||
"app_events_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/app_events",
|
||||
"events_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/events",
|
||||
"security_groups_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/security_groups",
|
||||
"staging_security_groups_url": "/v2/spaces/3d2eba6b-ef19-44d5-91dd-1975b0db5cc9/staging_security_groups"
|
||||
}
|
||||
}`
|
||||
|
||||
unfoundSpaceResponse = `{
|
||||
"description": "The app space could not be found: space-id-unfound",
|
||||
"error_code": "CF-SpaceNotFound",
|
||||
"code": 40004
|
||||
}`
|
||||
)
|
79
vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/certificates.go
generated
vendored
Normal file
79
vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/certificates.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
// ExtractCertificates takes the contents of the file at CF_INSTANCE_CERT, which typically are
|
||||
// comprised of two certificates. One is the identity certificate, and one is an intermediate
|
||||
// CA certificate which is crucial in linking the identity cert back to the configured root
|
||||
// certificate. It splits these two certificates apart, and identifies the certificate marked
|
||||
// as a CA as the intermediate cert, and the one not marked as a CA as the identity certificate.
|
||||
// It may error if the given file contents or certificates aren't as expected.
|
||||
func ExtractCertificates(cfInstanceCertContents string) (intermediateCert, identityCert *x509.Certificate, err error) {
|
||||
certPairBytes := []byte(cfInstanceCertContents)
|
||||
numCerts := 0
|
||||
var block *pem.Block
|
||||
var result error
|
||||
for {
|
||||
block, certPairBytes = pem.Decode(certPairBytes)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
certs, err := x509.ParseCertificates(block.Bytes)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
for _, cert := range certs {
|
||||
if cert.IsCA {
|
||||
intermediateCert = cert
|
||||
} else {
|
||||
identityCert = cert
|
||||
}
|
||||
numCerts++
|
||||
}
|
||||
}
|
||||
if numCerts != 2 {
|
||||
result = multierror.Append(fmt.Errorf("expected 2 certs but received %s", certPairBytes))
|
||||
}
|
||||
if intermediateCert == nil {
|
||||
result = multierror.Append(fmt.Errorf("no intermediate certificate found in %s", certPairBytes))
|
||||
}
|
||||
if identityCert == nil {
|
||||
result = multierror.Append(fmt.Errorf("no identity cert found in %s", certPairBytes))
|
||||
}
|
||||
return intermediateCert, identityCert, result
|
||||
}
|
||||
|
||||
// Validate takes a group of trusted CA certificates, an intermediate certificate, an identity certificate,
|
||||
// and a signing certificate, and makes sure they have the following properties:
|
||||
// - The identity certificate is the same as the signing certificate
|
||||
// - The identity certificate chains to at least one trusted CA
|
||||
func Validate(caCerts []string, intermediateCert, identityCert, signingCert *x509.Certificate) error {
|
||||
if !reflect.DeepEqual(identityCert, signingCert) {
|
||||
return errors.New("signature not generated by identity cert")
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
for _, caCert := range caCerts {
|
||||
if ok := roots.AppendCertsFromPEM([]byte(caCert)); !ok {
|
||||
return errors.New("couldn't append root certificate")
|
||||
}
|
||||
}
|
||||
intermediates := x509.NewCertPool()
|
||||
intermediates.AddCert(intermediateCert)
|
||||
verifyOpts := x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
Intermediates: intermediates,
|
||||
}
|
||||
if _, err := signingCert.Verify(verifyOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cloudfoundry-community/go-cfclient"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/vault-plugin-auth-pcf/models"
|
||||
)
|
||||
|
||||
const BashTimeFormat = "Mon Jan 2 15:04:05 MST 2006"
|
||||
|
||||
// NewPCFClient does some work that's needed every time we use the PCF client,
|
||||
// namely using cleanhttp and configuring it to match the user conf.
|
||||
func NewPCFClient(config *models.Configuration) (*cfclient.Client, error) {
|
||||
clientConf := &cfclient.Config{
|
||||
ApiAddress: config.PCFAPIAddr,
|
||||
Username: config.PCFUsername,
|
||||
Password: config.PCFPassword,
|
||||
HttpClient: cleanhttp.DefaultClient(),
|
||||
}
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
for _, certificate := range config.PCFAPICertificates {
|
||||
if ok := rootCAs.AppendCertsFromPEM([]byte(certificate)); !ok {
|
||||
return nil, fmt.Errorf("couldn't append PCF API cert to trust: %s", certificate)
|
||||
}
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
clientConf.HttpClient.Transport = &http.Transport{TLSClientConfig: tlsConfig}
|
||||
return cfclient.NewClient(clientConf)
|
||||
}
|
120
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go
generated
vendored
Normal file
120
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package clientcredentials implements the OAuth2.0 "client credentials" token flow,
|
||||
// also known as the "two-legged OAuth 2.0".
|
||||
//
|
||||
// This should be used when the client is acting on its own behalf or when the client
|
||||
// is the resource owner. It may also be used when requesting access to protected
|
||||
// resources based on an authorization previously arranged with the authorization
|
||||
// server.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc6749#section-4.4
|
||||
package clientcredentials // import "golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
// Config describes a 2-legged OAuth2 flow, with both the
|
||||
// client application information and the server's endpoint URLs.
|
||||
type Config struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
|
||||
// ClientSecret is the application's secret.
|
||||
ClientSecret string
|
||||
|
||||
// TokenURL is the resource server's token endpoint
|
||||
// URL. This is a constant specific to each server.
|
||||
TokenURL string
|
||||
|
||||
// Scope specifies optional requested permissions.
|
||||
Scopes []string
|
||||
|
||||
// EndpointParams specifies additional parameters for requests to the token endpoint.
|
||||
EndpointParams url.Values
|
||||
|
||||
// AuthStyle optionally specifies how the endpoint wants the
|
||||
// client ID & client secret sent. The zero value means to
|
||||
// auto-detect.
|
||||
AuthStyle oauth2.AuthStyle
|
||||
}
|
||||
|
||||
// Token uses client credentials to retrieve a token.
|
||||
//
|
||||
// The provided context optionally controls which HTTP client is used. See the oauth2.HTTPClient variable.
|
||||
func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) {
|
||||
return c.TokenSource(ctx).Token()
|
||||
}
|
||||
|
||||
// Client returns an HTTP client using the provided token.
|
||||
// The token will auto-refresh as necessary.
|
||||
//
|
||||
// The provided context optionally controls which HTTP client
|
||||
// is returned. See the oauth2.HTTPClient variable.
|
||||
//
|
||||
// The returned Client and its Transport should not be modified.
|
||||
func (c *Config) Client(ctx context.Context) *http.Client {
|
||||
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||
}
|
||||
|
||||
// TokenSource returns a TokenSource that returns t until t expires,
|
||||
// automatically refreshing it as necessary using the provided context and the
|
||||
// client ID and client secret.
|
||||
//
|
||||
// Most users will use Config.Client instead.
|
||||
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||
source := &tokenSource{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
}
|
||||
return oauth2.ReuseTokenSource(nil, source)
|
||||
}
|
||||
|
||||
type tokenSource struct {
|
||||
ctx context.Context
|
||||
conf *Config
|
||||
}
|
||||
|
||||
// Token refreshes the token by using a new client credentials request.
|
||||
// tokens received this way do not include a refresh token
|
||||
func (c *tokenSource) Token() (*oauth2.Token, error) {
|
||||
v := url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
}
|
||||
if len(c.conf.Scopes) > 0 {
|
||||
v.Set("scope", strings.Join(c.conf.Scopes, " "))
|
||||
}
|
||||
for k, p := range c.conf.EndpointParams {
|
||||
// Allow grant_type to be overridden to allow interoperability with
|
||||
// non-compliant implementations.
|
||||
if _, ok := v[k]; ok && k != "grant_type" {
|
||||
return nil, fmt.Errorf("oauth2: cannot overwrite parameter %q", k)
|
||||
}
|
||||
v[k] = p
|
||||
}
|
||||
|
||||
tk, err := internal.RetrieveToken(c.ctx, c.conf.ClientID, c.conf.ClientSecret, c.conf.TokenURL, v, internal.AuthStyle(c.conf.AuthStyle))
|
||||
if err != nil {
|
||||
if rErr, ok := err.(*internal.RetrieveError); ok {
|
||||
return nil, (*oauth2.RetrieveError)(rErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
t := &oauth2.Token{
|
||||
AccessToken: tk.AccessToken,
|
||||
TokenType: tk.TokenType,
|
||||
RefreshToken: tk.RefreshToken,
|
||||
Expiry: tk.Expiry,
|
||||
}
|
||||
return t.WithExtra(tk.Raw), nil
|
||||
}
|
|
@ -13,6 +13,8 @@ cloud.google.com/go/internal/protostruct
|
|||
cloud.google.com/go/internal/testutil
|
||||
cloud.google.com/go/spanner/internal/backoff
|
||||
cloud.google.com/go/compute/metadata
|
||||
# code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f
|
||||
code.cloudfoundry.org/gofileutils/fileutils
|
||||
# contrib.go.opencensus.io/exporter/ocagent v0.4.12
|
||||
contrib.go.opencensus.io/exporter/ocagent
|
||||
# github.com/Azure/azure-sdk-for-go v27.1.0+incompatible
|
||||
|
@ -40,6 +42,8 @@ github.com/Azure/go-autorest/autorest/azure/cli
|
|||
github.com/DataDog/datadog-go/statsd
|
||||
# github.com/Jeffail/gabs v1.1.1
|
||||
github.com/Jeffail/gabs
|
||||
# github.com/Masterminds/semver v1.4.2
|
||||
github.com/Masterminds/semver
|
||||
# github.com/Microsoft/go-winio v0.4.12
|
||||
github.com/Microsoft/go-winio
|
||||
# github.com/NYTimes/gziphandler v1.1.1
|
||||
|
@ -168,6 +172,8 @@ github.com/circonus-labs/circonus-gometrics/checkmgr
|
|||
github.com/circonus-labs/circonus-gometrics/api/config
|
||||
# github.com/circonus-labs/circonusllhist v0.1.3
|
||||
github.com/circonus-labs/circonusllhist
|
||||
# github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/cloudfoundry-community/go-cfclient
|
||||
# github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c
|
||||
github.com/cockroachdb/cockroach-go/crdb
|
||||
# github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
|
||||
|
@ -273,7 +279,7 @@ github.com/hashicorp/errwrap
|
|||
github.com/hashicorp/go-cleanhttp
|
||||
# github.com/hashicorp/go-gcp-common v0.5.0
|
||||
github.com/hashicorp/go-gcp-common/gcputil
|
||||
# github.com/hashicorp/go-hclog v0.8.0
|
||||
# github.com/hashicorp/go-hclog v0.9.2
|
||||
github.com/hashicorp/go-hclog
|
||||
# github.com/hashicorp/go-immutable-radix v1.0.0
|
||||
github.com/hashicorp/go-immutable-radix
|
||||
|
@ -329,6 +335,13 @@ github.com/hashicorp/vault-plugin-auth-gcp/plugin/cache
|
|||
github.com/hashicorp/vault-plugin-auth-jwt
|
||||
# github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-kubernetes
|
||||
# github.com/hashicorp/vault-plugin-auth-pcf v0.0.0-20190619165123-fb996be2877c
|
||||
github.com/hashicorp/vault-plugin-auth-pcf
|
||||
github.com/hashicorp/vault-plugin-auth-pcf/signatures
|
||||
github.com/hashicorp/vault-plugin-auth-pcf/models
|
||||
github.com/hashicorp/vault-plugin-auth-pcf/util
|
||||
github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates
|
||||
github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf
|
||||
# github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190508211750-4152192cdc0f
|
||||
github.com/hashicorp/vault-plugin-database-elasticsearch
|
||||
# github.com/hashicorp/vault-plugin-secrets-ad v0.5.1
|
||||
|
@ -644,6 +657,7 @@ golang.org/x/oauth2/internal
|
|||
golang.org/x/oauth2/google
|
||||
golang.org/x/oauth2/jwt
|
||||
golang.org/x/oauth2/jws
|
||||
golang.org/x/oauth2/clientcredentials
|
||||
# golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6
|
||||
golang.org/x/sync/semaphore
|
||||
# golang.org/x/sys v0.0.0-20190410170021-cc4d4f50624c
|
||||
|
|
Loading…
Reference in New Issue