From a732b22f1181757b84a3ae884bafd6af130c869d Mon Sep 17 00:00:00 2001 From: Becca Petrin Date: Thu, 6 Jun 2019 12:26:04 -0700 Subject: [PATCH] add PCF auth method, agent, and cli handler --- command/agent.go | 3 + command/agent/auth/pcf/pcf.go | 82 + command/agent/pcf_end_to_end_test.go | 170 + command/commands.go | 2 + go.mod | 5 +- go.sum | 21 + helper/builtinplugins/registry.go | 2 + .../code.cloudfoundry.org/gofileutils/LICENSE | 202 ++ .../code.cloudfoundry.org/gofileutils/NOTICE | 11 + .../gofileutils/fileutils/dir_utils.go | 20 + .../gofileutils/fileutils/file_utils.go | 74 + .../gofileutils/fileutils/temp_utils.go | 27 + .../github.com/Masterminds/semver/.travis.yml | 27 + .../Masterminds/semver/CHANGELOG.md | 86 + .../github.com/Masterminds/semver/LICENSE.txt | 20 + vendor/github.com/Masterminds/semver/Makefile | 36 + .../github.com/Masterminds/semver/README.md | 165 + .../Masterminds/semver/appveyor.yml | 44 + .../Masterminds/semver/collection.go | 24 + .../Masterminds/semver/constraints.go | 426 +++ vendor/github.com/Masterminds/semver/doc.go | 115 + .../github.com/Masterminds/semver/version.go | 421 +++ .../go-cfclient/.gitignore | 30 + .../go-cfclient/.travis.yml | 14 + .../go-cfclient/Gopkg.lock | 174 + .../go-cfclient/Gopkg.toml | 58 + .../go-cfclient/LICENSE | 21 + .../go-cfclient/README.md | 59 + .../go-cfclient/app_update.go | 107 + .../go-cfclient/app_usage_events.go | 80 + .../go-cfclient/appevents.go | 182 + .../go-cfclient/apps.go | 659 ++++ .../go-cfclient/buildpacks.go | 247 ++ .../go-cfclient/cf_error.go | 3171 +++++++++++++++++ .../go-cfclient/client.go | 407 +++ .../go-cfclient/domains.go | 290 ++ .../go-cfclient/environmentvariablegroups.go | 59 + .../go-cfclient/error.go | 54 + .../go-cfclient/events.go | 94 + .../go-cfclient/gen_error.go | 115 + .../cloudfoundry-community/go-cfclient/go.mod | 20 + .../cloudfoundry-community/go-cfclient/go.sum | 65 + .../go-cfclient/info.go | 43 + .../go-cfclient/isolationsegments.go | 251 ++ .../go-cfclient/org_quotas.go | 184 + .../go-cfclient/orgs.go | 832 +++++ .../go-cfclient/processes.go | 124 + .../go-cfclient/route_mappings.go | 159 + .../go-cfclient/routes.go | 168 + .../go-cfclient/secgroups.go | 565 +++ .../go-cfclient/service_bindings.go | 176 + .../go-cfclient/service_brokers.go | 207 ++ .../go-cfclient/service_instances.go | 186 + .../go-cfclient/service_keys.go | 171 + .../go-cfclient/service_plan_visibilities.go | 169 + .../go-cfclient/service_plans.go | 129 + .../go-cfclient/service_usage_events.go | 72 + .../go-cfclient/services.go | 107 + .../go-cfclient/space_quotas.go | 183 + .../go-cfclient/spaces.go | 790 ++++ .../go-cfclient/stacks.go | 76 + .../go-cfclient/tasks.go | 204 ++ .../go-cfclient/types.go | 8 + .../user_provided_service_instances.go | 185 + .../go-cfclient/users.go | 201 ++ .../go-cfclient/v3types.go | 17 + .../github.com/hashicorp/go-hclog/context.go | 38 + .../github.com/hashicorp/go-hclog/global.go | 16 +- .../hashicorp/go-hclog/intlogger.go | 86 +- .../github.com/hashicorp/go-hclog/logger.go | 6 + .../github.com/hashicorp/go-hclog/stdlog.go | 23 +- .../vault-plugin-auth-pcf/.gitignore | 25 + .../hashicorp/vault-plugin-auth-pcf/LICENSE | 373 ++ .../hashicorp/vault-plugin-auth-pcf/Makefile | 60 + .../hashicorp/vault-plugin-auth-pcf/README.md | 228 ++ .../vault-plugin-auth-pcf/backend.go | 52 + .../hashicorp/vault-plugin-auth-pcf/cli.go | 115 + .../hashicorp/vault-plugin-auth-pcf/go.mod | 14 + .../hashicorp/vault-plugin-auth-pcf/go.sum | 168 + .../models/configuration.go | 56 + .../vault-plugin-auth-pcf/models/pcf_cert.go | 98 + .../vault-plugin-auth-pcf/models/role.go | 21 + .../vault-plugin-auth-pcf/path_config.go | 215 ++ .../vault-plugin-auth-pcf/path_login.go | 396 ++ .../vault-plugin-auth-pcf/path_roles.go | 275 ++ .../signatures/version1.go | 137 + .../testing/certificates/generate.go | 237 ++ .../vault-plugin-auth-pcf/testing/pcf/mock.go | 292 ++ .../vault-plugin-auth-pcf/util/util.go | 3 + .../clientcredentials/clientcredentials.go | 120 + vendor/modules.txt | 18 +- 91 files changed, 16127 insertions(+), 41 deletions(-) create mode 100644 command/agent/auth/pcf/pcf.go create mode 100644 command/agent/pcf_end_to_end_test.go create mode 100644 vendor/code.cloudfoundry.org/gofileutils/LICENSE create mode 100644 vendor/code.cloudfoundry.org/gofileutils/NOTICE create mode 100644 vendor/code.cloudfoundry.org/gofileutils/fileutils/dir_utils.go create mode 100644 vendor/code.cloudfoundry.org/gofileutils/fileutils/file_utils.go create mode 100644 vendor/code.cloudfoundry.org/gofileutils/fileutils/temp_utils.go create mode 100644 vendor/github.com/Masterminds/semver/.travis.yml create mode 100644 vendor/github.com/Masterminds/semver/CHANGELOG.md create mode 100644 vendor/github.com/Masterminds/semver/LICENSE.txt create mode 100644 vendor/github.com/Masterminds/semver/Makefile create mode 100644 vendor/github.com/Masterminds/semver/README.md create mode 100644 vendor/github.com/Masterminds/semver/appveyor.yml create mode 100644 vendor/github.com/Masterminds/semver/collection.go create mode 100644 vendor/github.com/Masterminds/semver/constraints.go create mode 100644 vendor/github.com/Masterminds/semver/doc.go create mode 100644 vendor/github.com/Masterminds/semver/version.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/.gitignore create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/.travis.yml create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.lock create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.toml create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/LICENSE create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/README.md create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/app_update.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/app_usage_events.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/appevents.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/apps.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/buildpacks.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/cf_error.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/client.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/domains.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/environmentvariablegroups.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/error.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/events.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/gen_error.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/go.mod create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/go.sum create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/info.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/isolationsegments.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/org_quotas.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/orgs.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/processes.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/route_mappings.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/routes.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/secgroups.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/service_bindings.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/service_brokers.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/service_instances.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/service_keys.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/service_plan_visibilities.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/service_plans.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/service_usage_events.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/services.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/space_quotas.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/spaces.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/stacks.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/tasks.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/types.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/user_provided_service_instances.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/users.go create mode 100644 vendor/github.com/cloudfoundry-community/go-cfclient/v3types.go create mode 100644 vendor/github.com/hashicorp/go-hclog/context.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/.gitignore create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/LICENSE create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/Makefile create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/README.md create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/backend.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/cli.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.mod create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.sum create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/configuration.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/pcf_cert.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/role.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_config.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_login.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_roles.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/signatures/version1.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf/mock.go create mode 100644 vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/util.go create mode 100644 vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go diff --git a/command/agent.go b/command/agent.go index 2884004441..7ce58e3238 100644 --- a/command/agent.go +++ b/command/agent.go @@ -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 diff --git a/command/agent/auth/pcf/pcf.go b/command/agent/auth/pcf/pcf.go new file mode 100644 index 0000000000..115b91bcba --- /dev/null +++ b/command/agent/auth/pcf/pcf.go @@ -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, + Certificate: string(certBytes), + } + signature, err := signatures.Sign(pathToClientKey, signatureData) + if err != nil { + return "", nil, err + } + data := map[string]interface{}{ + "role": p.roleName, + "certificate": 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() {} diff --git a/command/agent/pcf_end_to_end_test.go b/command/agent/pcf_end_to_end_test.go new file mode 100644 index 0000000000..d5641b59af --- /dev/null +++ b/command/agent/pcf_end_to_end_test.go @@ -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{}{ + "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") + } +} diff --git a/command/commands.go b/command/commands.go index 28690a1942..95ec5e5239 100644 --- a/command/commands.go +++ b/command/commands.go @@ -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", }, diff --git a/go.mod b/go.mod index b1de25ed4f..2719bc9b06 100644 --- a/go.mod +++ b/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,13 +74,14 @@ 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-20190605234735-619218abcd26 github.com/hashicorp/vault-plugin-secrets-ad v0.5.1 github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.1 github.com/hashicorp/vault-plugin-secrets-azure v0.5.1 github.com/hashicorp/vault-plugin-secrets-gcp v0.5.2 github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.1 github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20190416155133-fd495225dea0 - github.com/hashicorp/vault/api v1.0.1 + github.com/hashicorp/vault/api v1.0.2 github.com/hashicorp/vault/sdk v0.1.11 github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4 github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect diff --git a/go.sum b/go.sum index 1e6442fedc..8139b1728e 100644 --- a/go.sum +++ b/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= @@ -190,6 +198,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= @@ -225,6 +234,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= @@ -275,6 +286,10 @@ 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-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= @@ -316,6 +331,7 @@ 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= @@ -337,6 +353,7 @@ github.com/lib/pq v1.0.0/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 +416,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 +476,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 +554,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= diff --git a/helper/builtinplugins/registry.go b/helper/builtinplugins/registry.go index 699f3d6fcd..7675117b12 100644 --- a/helper/builtinplugins/registry.go +++ b/helper/builtinplugins/registry.go @@ -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" @@ -75,6 +76,7 @@ func newRegistry() *registry { "ldap": credLdap.Factory, "oidc": credJWT.Factory, "okta": credOkta.Factory, + "pcf": credPCF.Factory, "radius": credRadius.Factory, "userpass": credUserpass.Factory, }, diff --git a/vendor/code.cloudfoundry.org/gofileutils/LICENSE b/vendor/code.cloudfoundry.org/gofileutils/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/code.cloudfoundry.org/gofileutils/LICENSE @@ -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. diff --git a/vendor/code.cloudfoundry.org/gofileutils/NOTICE b/vendor/code.cloudfoundry.org/gofileutils/NOTICE new file mode 100644 index 0000000000..af7e5e66e1 --- /dev/null +++ b/vendor/code.cloudfoundry.org/gofileutils/NOTICE @@ -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. diff --git a/vendor/code.cloudfoundry.org/gofileutils/fileutils/dir_utils.go b/vendor/code.cloudfoundry.org/gofileutils/fileutils/dir_utils.go new file mode 100644 index 0000000000..a430217ec5 --- /dev/null +++ b/vendor/code.cloudfoundry.org/gofileutils/fileutils/dir_utils.go @@ -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 +} diff --git a/vendor/code.cloudfoundry.org/gofileutils/fileutils/file_utils.go b/vendor/code.cloudfoundry.org/gofileutils/fileutils/file_utils.go new file mode 100644 index 0000000000..ac9b6eb4e0 --- /dev/null +++ b/vendor/code.cloudfoundry.org/gofileutils/fileutils/file_utils.go @@ -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 +} diff --git a/vendor/code.cloudfoundry.org/gofileutils/fileutils/temp_utils.go b/vendor/code.cloudfoundry.org/gofileutils/fileutils/temp_utils.go new file mode 100644 index 0000000000..4961a8d40b --- /dev/null +++ b/vendor/code.cloudfoundry.org/gofileutils/fileutils/temp_utils.go @@ -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) +} diff --git a/vendor/github.com/Masterminds/semver/.travis.yml b/vendor/github.com/Masterminds/semver/.travis.yml new file mode 100644 index 0000000000..3d9ebadb93 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/.travis.yml @@ -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 diff --git a/vendor/github.com/Masterminds/semver/CHANGELOG.md b/vendor/github.com/Masterminds/semver/CHANGELOG.md new file mode 100644 index 0000000000..b888e20aba --- /dev/null +++ b/vendor/github.com/Masterminds/semver/CHANGELOG.md @@ -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 diff --git a/vendor/github.com/Masterminds/semver/LICENSE.txt b/vendor/github.com/Masterminds/semver/LICENSE.txt new file mode 100644 index 0000000000..0da4aeadb0 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/LICENSE.txt @@ -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. diff --git a/vendor/github.com/Masterminds/semver/Makefile b/vendor/github.com/Masterminds/semver/Makefile new file mode 100644 index 0000000000..a7a1b4e36d --- /dev/null +++ b/vendor/github.com/Masterminds/semver/Makefile @@ -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 \ + ./... || : diff --git a/vendor/github.com/Masterminds/semver/README.md b/vendor/github.com/Masterminds/semver/README.md new file mode 100644 index 0000000000..3e934ed71e --- /dev/null +++ b/vendor/github.com/Masterminds/semver/README.md @@ -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). diff --git a/vendor/github.com/Masterminds/semver/appveyor.yml b/vendor/github.com/Masterminds/semver/appveyor.yml new file mode 100644 index 0000000000..b2778df15a --- /dev/null +++ b/vendor/github.com/Masterminds/semver/appveyor.yml @@ -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 diff --git a/vendor/github.com/Masterminds/semver/collection.go b/vendor/github.com/Masterminds/semver/collection.go new file mode 100644 index 0000000000..a78235895f --- /dev/null +++ b/vendor/github.com/Masterminds/semver/collection.go @@ -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] +} diff --git a/vendor/github.com/Masterminds/semver/constraints.go b/vendor/github.com/Masterminds/semver/constraints.go new file mode 100644 index 0000000000..a41a6a7a4a --- /dev/null +++ b/vendor/github.com/Masterminds/semver/constraints.go @@ -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 +} diff --git a/vendor/github.com/Masterminds/semver/doc.go b/vendor/github.com/Masterminds/semver/doc.go new file mode 100644 index 0000000000..e00f65eb73 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/doc.go @@ -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 diff --git a/vendor/github.com/Masterminds/semver/version.go b/vendor/github.com/Masterminds/semver/version.go new file mode 100644 index 0000000000..9d22ea6308 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/version.go @@ -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 + +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/.gitignore b/vendor/github.com/cloudfoundry-community/go-cfclient/.gitignore new file mode 100644 index 0000000000..06e1eff750 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/.gitignore @@ -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 \ No newline at end of file diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/.travis.yml b/vendor/github.com/cloudfoundry-community/go-cfclient/.travis.yml new file mode 100644 index 0000000000..6881df0306 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/.travis.yml @@ -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 diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.lock b/vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.lock new file mode 100644 index 0000000000..0d18c2e0be --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.lock @@ -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 diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.toml b/vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.toml new file mode 100644 index 0000000000..5b0d1610cf --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/Gopkg.toml @@ -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" diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/LICENSE b/vendor/github.com/cloudfoundry-community/go-cfclient/LICENSE new file mode 100644 index 0000000000..cb2ec6c50d --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/LICENSE @@ -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. diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/README.md b/vendor/github.com/cloudfoundry-community/go-cfclient/README.md new file mode 100644 index 0000000000..efae2d8806 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/README.md @@ -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 +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. diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/app_update.go b/vendor/github.com/cloudfoundry-community/go-cfclient/app_update.go new file mode 100644 index 0000000000..ef023228ab --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/app_update.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/app_usage_events.go b/vendor/github.com/cloudfoundry-community/go-cfclient/app_usage_events.go new file mode 100644 index 0000000000..227c4fe324 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/app_usage_events.go @@ -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) +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/appevents.go b/vendor/github.com/cloudfoundry-community/go-cfclient/appevents.go new file mode 100644 index 0000000000..8784c62e79 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/appevents.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/apps.go b/vendor/github.com/cloudfoundry-community/go-cfclient/apps.go new file mode 100644 index 0000000000..69c06f1ef9 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/apps.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/buildpacks.go b/vendor/github.com/cloudfoundry-community/go-cfclient/buildpacks.go new file mode 100644 index 0000000000..0b1a09e66c --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/buildpacks.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/cf_error.go b/vendor/github.com/cloudfoundry-community/go-cfclient/cf_error.go new file mode 100644 index 0000000000..6555519acb --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/cf_error.go @@ -0,0 +1,3171 @@ +package cfclient + +// Code generated by go generate. DO NOT EDIT. +// This file was generated by robots at +// 2017-11-23 06:47:40.143734 +1100 AEDT m=+2.237010727 + +import "github.com/pkg/errors" + +// IsInvalidAuthTokenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 1000 +// - HTTP code: 401 +// - message: "Invalid Auth Token" +func IsInvalidAuthTokenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 1000 +} + +// IsMessageParseError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 1001 +// - HTTP code: 400 +// - message: "Request invalid due to parse error: %s" +func IsMessageParseError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 1001 +} + +// IsInvalidRelationError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 1002 +// - HTTP code: 400 +// - message: "%s" +func IsInvalidRelationError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 1002 +} + +// IsInvalidContentTypeError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 1003 +// - HTTP code: 400 +// - message: "Invalid content type, expected: %s" +func IsInvalidContentTypeError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 1003 +} + +// IsNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10000 +// - HTTP code: 404 +// - message: "Unknown request" +func IsNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10000 +} + +// IsServerError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10001 +// - HTTP code: 500 +// - message: "Server error" +func IsServerError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10001 +} + +// IsNotAuthenticatedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10002 +// - HTTP code: 401 +// - message: "Authentication error" +func IsNotAuthenticatedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10002 +} + +// IsNotAuthorizedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10003 +// - HTTP code: 403 +// - message: "You are not authorized to perform the requested action" +func IsNotAuthorizedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10003 +} + +// IsInvalidRequestError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10004 +// - HTTP code: 400 +// - message: "The request is invalid" +func IsInvalidRequestError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10004 +} + +// IsBadQueryParameterError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10005 +// - HTTP code: 400 +// - message: "The query parameter is invalid: %s" +func IsBadQueryParameterError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10005 +} + +// IsAssociationNotEmptyError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10006 +// - HTTP code: 400 +// - message: "Please delete the %s associations for your %s." +func IsAssociationNotEmptyError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10006 +} + +// IsInsufficientScopeError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10007 +// - HTTP code: 403 +// - message: "Your token lacks the necessary scopes to access this resource." +func IsInsufficientScopeError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10007 +} + +// IsUnprocessableEntityError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10008 +// - HTTP code: 422 +// - message: "%s" +func IsUnprocessableEntityError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10008 +} + +// IsUnableToPerformError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10009 +// - HTTP code: 400 +// - message: "%s could not be completed: %s" +func IsUnableToPerformError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10009 +} + +// IsResourceNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10010 +// - HTTP code: 404 +// - message: "%s" +func IsResourceNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10010 +} + +// IsDatabaseError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10011 +// - HTTP code: 500 +// - message: "Database error" +func IsDatabaseError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10011 +} + +// IsOrderByParameterInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10012 +// - HTTP code: 500 +// - message: "Cannot order by: %s" +func IsOrderByParameterInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10012 +} + +// IsRateLimitExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 10013 +// - HTTP code: 429 +// - message: "Rate Limit Exceeded" +func IsRateLimitExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 10013 +} + +// IsUserInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 20001 +// - HTTP code: 400 +// - message: "The user info is invalid: %s" +func IsUserInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 20001 +} + +// IsUaaIdTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 20002 +// - HTTP code: 400 +// - message: "The UAA ID is taken: %s" +func IsUaaIdTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 20002 +} + +// IsUserNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 20003 +// - HTTP code: 404 +// - message: "The user could not be found: %s" +func IsUserNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 20003 +} + +// IsUaaUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 20004 +// - HTTP code: 503 +// - message: "The UAA service is currently unavailable" +func IsUaaUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 20004 +} + +// IsUaaEndpointDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 20005 +// - HTTP code: 501 +// - message: "The UAA endpoint needed is disabled" +func IsUaaEndpointDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 20005 +} + +// IsUserIsInMultipleOriginsError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 20006 +// - HTTP code: 400 +// - message: "The user exists in multiple origins. Specify an origin for the requested user from: %s" +func IsUserIsInMultipleOriginsError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 20006 +} + +// IsUserWithOriginNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 20007 +// - HTTP code: 404 +// - message: "The user could not be found, %s" +func IsUserWithOriginNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 20007 +} + +// IsOutOfRouterGroupPortsError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 21008 +// - HTTP code: 403 +// - message: "There are no more ports available for router group: %s. Please contact your administrator for more information." +func IsOutOfRouterGroupPortsError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 21008 +} + +// IsOrganizationInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 30001 +// - HTTP code: 400 +// - message: "The organization info is invalid: %s" +func IsOrganizationInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 30001 +} + +// IsOrganizationNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 30002 +// - HTTP code: 400 +// - message: "The organization name is taken: %s" +func IsOrganizationNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 30002 +} + +// IsOrganizationNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 30003 +// - HTTP code: 404 +// - message: "The organization could not be found: %s" +func IsOrganizationNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 30003 +} + +// IsLastManagerInOrgError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 30004 +// - HTTP code: 403 +// - message: "Cannot remove last Org Manager in org" +func IsLastManagerInOrgError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 30004 +} + +// IsLastBillingManagerInOrgError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 30005 +// - HTTP code: 403 +// - message: "Cannot remove last Billing Manager in org" +func IsLastBillingManagerInOrgError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 30005 +} + +// IsLastUserInOrgError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 30006 +// - HTTP code: 403 +// - message: "Cannot remove last User in org" +func IsLastUserInOrgError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 30006 +} + +// IsOrganizationAlreadySetError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 30007 +// - HTTP code: 400 +// - message: "Cannot change organization" +func IsOrganizationAlreadySetError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 30007 +} + +// IsSpaceInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 40001 +// - HTTP code: 400 +// - message: "The app space info is invalid: %s" +func IsSpaceInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 40001 +} + +// IsSpaceNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 40002 +// - HTTP code: 400 +// - message: "The app space name is taken: %s" +func IsSpaceNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 40002 +} + +// IsSpaceUserNotInOrgError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 40003 +// - HTTP code: 400 +// - message: "The app space and the user are not in the same org: %s" +func IsSpaceUserNotInOrgError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 40003 +} + +// IsSpaceNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 40004 +// - HTTP code: 404 +// - message: "The app space could not be found: %s" +func IsSpaceNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 40004 +} + +// IsServiceInstanceNameEmptyError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60001 +// - HTTP code: 400 +// - message: "Service instance name is required." +func IsServiceInstanceNameEmptyError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60001 +} + +// IsServiceInstanceNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60002 +// - HTTP code: 400 +// - message: "The service instance name is taken: %s" +func IsServiceInstanceNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60002 +} + +// IsServiceInstanceInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60003 +// - HTTP code: 400 +// - message: "The service instance is invalid: %s" +func IsServiceInstanceInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60003 +} + +// IsServiceInstanceNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60004 +// - HTTP code: 404 +// - message: "The service instance could not be found: %s" +func IsServiceInstanceNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60004 +} + +// IsServiceInstanceQuotaExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60005 +// - HTTP code: 400 +// - message: "You have exceeded your organization's services limit." +func IsServiceInstanceQuotaExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60005 +} + +// IsPreviouslyUsedAs_ServiceInstancePaidQuotaExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60006 +// - HTTP code: 400 +// - message: "You have exceeded your organization's services limit." +func IsPreviouslyUsedAs_ServiceInstancePaidQuotaExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60006 +} + +// IsServiceInstanceServicePlanNotAllowedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60007 +// - HTTP code: 400 +// - message: "The service instance cannot be created because paid service plans are not allowed." +func IsServiceInstanceServicePlanNotAllowedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60007 +} + +// IsServiceInstanceDuplicateNotAllowedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60008 +// - HTTP code: 400 +// - message: "An instance of this service is already present in this space. Some services only support one instance per space." +func IsServiceInstanceDuplicateNotAllowedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60008 +} + +// IsServiceInstanceNameTooLongError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60009 +// - HTTP code: 400 +// - message: "You have requested an invalid service instance name. Names are limited to 50 characters." +func IsServiceInstanceNameTooLongError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60009 +} + +// IsServiceInstanceOrganizationNotAuthorizedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60010 +// - HTTP code: 403 +// - message: "A service instance for the selected plan cannot be created in this organization. The plan is visible because another organization you belong to has access to it." +func IsServiceInstanceOrganizationNotAuthorizedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60010 +} + +// IsServiceInstanceDeprovisionFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60011 +// - HTTP code: 409 +// - message: "The service broker reported an error during deprovisioning: %s" +func IsServiceInstanceDeprovisionFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60011 +} + +// IsServiceInstanceSpaceQuotaExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60012 +// - HTTP code: 400 +// - message: "You have exceeded your space's services limit." +func IsServiceInstanceSpaceQuotaExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60012 +} + +// IsServiceInstanceServicePlanNotAllowedBySpaceQuotaError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60013 +// - HTTP code: 400 +// - message: "The service instance cannot be created because paid service plans are not allowed for your space." +func IsServiceInstanceServicePlanNotAllowedBySpaceQuotaError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60013 +} + +// IsServiceInstanceSpaceChangeNotAllowedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60014 +// - HTTP code: 400 +// - message: "Cannot update space for service instance." +func IsServiceInstanceSpaceChangeNotAllowedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60014 +} + +// IsServiceInstanceTagsTooLongError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60015 +// - HTTP code: 400 +// - message: "Combined length of tags for service %s must be 2048 characters or less." +func IsServiceInstanceTagsTooLongError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60015 +} + +// IsAsyncServiceInstanceOperationInProgressError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60016 +// - HTTP code: 409 +// - message: "An operation for service instance %s is in progress." +func IsAsyncServiceInstanceOperationInProgressError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60016 +} + +// IsServiceInstanceRouteBindingSpaceMismatchError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60017 +// - HTTP code: 400 +// - message: "The service instance and the route are in different spaces." +func IsServiceInstanceRouteBindingSpaceMismatchError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60017 +} + +// IsServiceInstanceSpaceNotAuthorizedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60018 +// - HTTP code: 403 +// - message: "A service instance for the selected plan cannot be created in this space." +func IsServiceInstanceSpaceNotAuthorizedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60018 +} + +// IsServiceInstanceRouteServiceURLInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60019 +// - HTTP code: 400 +// - message: "The route service URL is invalid: %s" +func IsServiceInstanceRouteServiceURLInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60019 +} + +// IsServiceInstanceRouteServiceRequiresDiegoError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60020 +// - HTTP code: 400 +// - message: "Route services are only supported for apps on Diego. Unbind the service instance from the route or enable Diego for the app." +func IsServiceInstanceRouteServiceRequiresDiegoError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60020 +} + +// IsServiceInstanceRouteServiceDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60021 +// - HTTP code: 403 +// - message: "Support for route services is disabled" +func IsServiceInstanceRouteServiceDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60021 +} + +// IsAppPortMappingRequiresDiegoError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60022 +// - HTTP code: 400 +// - message: "App ports are supported for Diego apps only." +func IsAppPortMappingRequiresDiegoError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60022 +} + +// IsRoutePortNotEnabledOnAppError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60023 +// - HTTP code: 400 +// - message: "Routes can only be mapped to ports already enabled for the application." +func IsRoutePortNotEnabledOnAppError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60023 +} + +// IsMultipleAppPortsMappedDiegoToDeaError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60024 +// - HTTP code: 400 +// - message: "The app has routes mapped to multiple ports. Multiple ports are supported for Diego only. Please unmap routes from all but one app port. Multiple routes can be mapped to the same port if desired." +func IsMultipleAppPortsMappedDiegoToDeaError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60024 +} + +// IsVolumeMountServiceDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60025 +// - HTTP code: 403 +// - message: "Support for volume mount services is disabled" +func IsVolumeMountServiceDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60025 +} + +// IsDockerAppToDeaError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 60026 +// - HTTP code: 400 +// - message: "Docker apps cannot run on DEAs" +func IsDockerAppToDeaError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 60026 +} + +// IsRuntimeInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 70001 +// - HTTP code: 400 +// - message: "The runtime is invalid: %s" +func IsRuntimeInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 70001 +} + +// IsRuntimeNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 70002 +// - HTTP code: 400 +// - message: "The runtime name is taken: %s" +func IsRuntimeNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 70002 +} + +// IsRuntimeNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 70003 +// - HTTP code: 404 +// - message: "The runtime could not be found: %s" +func IsRuntimeNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 70003 +} + +// IsFrameworkInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 80001 +// - HTTP code: 400 +// - message: "The framework is invalid: %s" +func IsFrameworkInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 80001 +} + +// IsFrameworkNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 80002 +// - HTTP code: 400 +// - message: "The framework name is taken: %s" +func IsFrameworkNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 80002 +} + +// IsFrameworkNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 80003 +// - HTTP code: 404 +// - message: "The framework could not be found: %s" +func IsFrameworkNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 80003 +} + +// IsServiceBindingInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 90001 +// - HTTP code: 400 +// - message: "The service binding is invalid: %s" +func IsServiceBindingInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 90001 +} + +// IsServiceBindingDifferentSpacesError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 90002 +// - HTTP code: 400 +// - message: "The app and the service are not in the same app space: %s" +func IsServiceBindingDifferentSpacesError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 90002 +} + +// IsServiceBindingAppServiceTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 90003 +// - HTTP code: 400 +// - message: "%s" +func IsServiceBindingAppServiceTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 90003 +} + +// IsServiceBindingNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 90004 +// - HTTP code: 404 +// - message: "The service binding could not be found: %s" +func IsServiceBindingNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 90004 +} + +// IsUnbindableServiceError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 90005 +// - HTTP code: 400 +// - message: "The service instance doesn't support binding." +func IsUnbindableServiceError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 90005 +} + +// IsInvalidLoggingServiceBindingError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 90006 +// - HTTP code: 502 +// - message: "The service is attempting to stream logs from your application, but is not registered as a logging service. Please contact the service provider." +func IsInvalidLoggingServiceBindingError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 90006 +} + +// IsAppInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 100001 +// - HTTP code: 400 +// - message: "The app is invalid: %s" +func IsAppInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 100001 +} + +// IsAppNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 100002 +// - HTTP code: 400 +// - message: "The app name is taken: %s" +func IsAppNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 100002 +} + +// IsAppNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 100004 +// - HTTP code: 404 +// - message: "The app could not be found: %s" +func IsAppNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 100004 +} + +// IsAppMemoryQuotaExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 100005 +// - HTTP code: 400 +// - message: "You have exceeded your organization's memory limit: %s" +func IsAppMemoryQuotaExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 100005 +} + +// IsAppMemoryInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 100006 +// - HTTP code: 400 +// - message: "You have specified an invalid amount of memory for your application." +func IsAppMemoryInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 100006 +} + +// IsQuotaInstanceMemoryLimitExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 100007 +// - HTTP code: 400 +// - message: "You have exceeded the instance memory limit for your organization's quota." +func IsQuotaInstanceMemoryLimitExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 100007 +} + +// IsQuotaInstanceLimitExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 100008 +// - HTTP code: 400 +// - message: "You have exceeded the instance limit for your organization's quota." +func IsQuotaInstanceLimitExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 100008 +} + +// IsServicePlanInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 110001 +// - HTTP code: 400 +// - message: "The service plan is invalid: %s" +func IsServicePlanInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 110001 +} + +// IsServicePlanNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 110002 +// - HTTP code: 400 +// - message: "The service plan name is taken: %s" +func IsServicePlanNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 110002 +} + +// IsServicePlanNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 110003 +// - HTTP code: 404 +// - message: "The service plan could not be found: %s" +func IsServicePlanNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 110003 +} + +// IsServicePlanNotUpdateableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 110004 +// - HTTP code: 400 +// - message: "The service does not support changing plans." +func IsServicePlanNotUpdateableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 110004 +} + +// IsServiceInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 120001 +// - HTTP code: 400 +// - message: "The service is invalid: %s" +func IsServiceInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 120001 +} + +// IsServiceLabelTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 120002 +// - HTTP code: 400 +// - message: "The service label is taken: %s" +func IsServiceLabelTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 120002 +} + +// IsServiceNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 120003 +// - HTTP code: 404 +// - message: "The service could not be found: %s" +func IsServiceNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 120003 +} + +// IsDomainInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130001 +// - HTTP code: 400 +// - message: "The domain is invalid: %s" +func IsDomainInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130001 +} + +// IsDomainNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130002 +// - HTTP code: 404 +// - message: "The domain could not be found: %s" +func IsDomainNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130002 +} + +// IsDomainNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130003 +// - HTTP code: 400 +// - message: "The domain name is taken: %s" +func IsDomainNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130003 +} + +// IsPathInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130004 +// - HTTP code: 400 +// - message: "The path is invalid: %s" +func IsPathInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130004 +} + +// IsTotalPrivateDomainsExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130005 +// - HTTP code: 400 +// - message: "The number of private domains exceeds the quota for organization: %s" +func IsTotalPrivateDomainsExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130005 +} + +// IsServiceDoesNotSupportRoutesError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130006 +// - HTTP code: 400 +// - message: "This service does not support route binding." +func IsServiceDoesNotSupportRoutesError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130006 +} + +// IsRouteAlreadyBoundToServiceInstanceError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130007 +// - HTTP code: 400 +// - message: "A route may only be bound to a single service instance" +func IsRouteAlreadyBoundToServiceInstanceError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130007 +} + +// IsServiceInstanceAlreadyBoundToSameRouteError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 130008 +// - HTTP code: 400 +// - message: "The route and service instance are already bound." +func IsServiceInstanceAlreadyBoundToSameRouteError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 130008 +} + +// IsLegacyApiWithoutDefaultSpaceError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 140001 +// - HTTP code: 400 +// - message: "A legacy api call requiring a default app space was called, but no default app space is set for the user." +func IsLegacyApiWithoutDefaultSpaceError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 140001 +} + +// IsAppPackageInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150001 +// - HTTP code: 400 +// - message: "The app package is invalid: %s" +func IsAppPackageInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150001 +} + +// IsAppPackageNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150002 +// - HTTP code: 404 +// - message: "The app package could not be found: %s" +func IsAppPackageNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150002 +} + +// IsInsufficientRunningResourcesAvailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150003 +// - HTTP code: 503 +// - message: "One or more instances could not be started because of insufficient running resources." +func IsInsufficientRunningResourcesAvailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150003 +} + +// IsPackageBitsAlreadyUploadedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150004 +// - HTTP code: 400 +// - message: "Bits may be uploaded only once. Create a new package to upload different bits." +func IsPackageBitsAlreadyUploadedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150004 +} + +// IsBlobstoreNotLocalError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150005 +// - HTTP code: 400 +// - message: "Downloading blobs can only be done directly to the blobstore." +func IsBlobstoreNotLocalError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150005 +} + +// IsBlobstoreUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150006 +// - HTTP code: 502 +// - message: "Failed to perform operation due to blobstore unavailability." +func IsBlobstoreUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150006 +} + +// IsBlobstoreError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150007 +// - HTTP code: 500 +// - message: "Failed to perform blobstore operation after three retries." +func IsBlobstoreError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150007 +} + +// IsDockerImageMissingError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 150008 +// - HTTP code: 400 +// - message: "Docker credentials can only be supplied for apps with a 'docker_image'" +func IsDockerImageMissingError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 150008 +} + +// IsAppBitsUploadInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 160001 +// - HTTP code: 400 +// - message: "The app upload is invalid: %s" +func IsAppBitsUploadInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 160001 +} + +// IsAppBitsCopyInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 160002 +// - HTTP code: 400 +// - message: "The app copy is invalid: %s" +func IsAppBitsCopyInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 160002 +} + +// IsAppResourcesFileModeInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 160003 +// - HTTP code: 400 +// - message: "The resource file mode is invalid: %s" +func IsAppResourcesFileModeInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 160003 +} + +// IsAppResourcesFilePathInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 160004 +// - HTTP code: 400 +// - message: "The resource file path is invalid: %s" +func IsAppResourcesFilePathInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 160004 +} + +// IsStagingError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170001 +// - HTTP code: 400 +// - message: "Staging error: %s" +func IsStagingError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170001 +} + +// IsNotStagedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170002 +// - HTTP code: 400 +// - message: "App has not finished staging" +func IsNotStagedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170002 +} + +// IsNoAppDetectedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170003 +// - HTTP code: 400 +// - message: "An app was not successfully detected by any available buildpack" +func IsNoAppDetectedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170003 +} + +// IsBuildpackCompileFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170004 +// - HTTP code: 400 +// - message: "App staging failed in the buildpack compile phase" +func IsBuildpackCompileFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170004 +} + +// IsBuildpackReleaseFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170005 +// - HTTP code: 400 +// - message: "App staging failed in the buildpack release phase" +func IsBuildpackReleaseFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170005 +} + +// IsNoBuildpacksFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170006 +// - HTTP code: 400 +// - message: "There are no buildpacks available" +func IsNoBuildpacksFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170006 +} + +// IsStagingTimeExpiredError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170007 +// - HTTP code: 504 +// - message: "Staging time expired: %s" +func IsStagingTimeExpiredError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170007 +} + +// IsInsufficientResourcesError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170008 +// - HTTP code: 400 +// - message: "Insufficient resources" +func IsInsufficientResourcesError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170008 +} + +// IsNoCompatibleCellError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170009 +// - HTTP code: 400 +// - message: "Found no compatible cell" +func IsNoCompatibleCellError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170009 +} + +// IsStagerUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170010 +// - HTTP code: 503 +// - message: "Stager is unavailable: %s" +func IsStagerUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170010 +} + +// IsStagerError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170011 +// - HTTP code: 500 +// - message: "Stager error: %s" +func IsStagerError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170011 +} + +// IsRunnerInvalidRequestError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170014 +// - HTTP code: 500 +// - message: "Runner invalid request: %s" +func IsRunnerInvalidRequestError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170014 +} + +// IsRunnerUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170015 +// - HTTP code: 503 +// - message: "Runner is unavailable: %s" +func IsRunnerUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170015 +} + +// IsRunnerError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170016 +// - HTTP code: 500 +// - message: "Runner error: %s" +func IsRunnerError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170016 +} + +// IsStagingInProgressError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170017 +// - HTTP code: 422 +// - message: "Only one build can be STAGING at a time per application." +func IsStagingInProgressError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170017 +} + +// IsInvalidTaskAddressError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170018 +// - HTTP code: 500 +// - message: "Invalid config: %s" +func IsInvalidTaskAddressError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170018 +} + +// IsTaskError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170019 +// - HTTP code: 500 +// - message: "Task failed: %s" +func IsTaskError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170019 +} + +// IsTaskWorkersUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170020 +// - HTTP code: 503 +// - message: "Task workers are unavailable: %s" +func IsTaskWorkersUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170020 +} + +// IsInvalidTaskRequestError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 170021 +// - HTTP code: 422 +// - message: "The task request is invalid: %s" +func IsInvalidTaskRequestError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 170021 +} + +// IsServiceGatewayError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 180002 +// - HTTP code: 503 +// - message: "Service gateway internal error: %s" +func IsServiceGatewayError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 180002 +} + +// IsServiceNotImplementedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 180003 +// - HTTP code: 501 +// - message: "Operation not supported for service" +func IsServiceNotImplementedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 180003 +} + +// IsSDSNotAvailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 180004 +// - HTTP code: 501 +// - message: "No serialization service backends available" +func IsSDSNotAvailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 180004 +} + +// IsFileError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 190001 +// - HTTP code: 400 +// - message: "File error: %s" +func IsFileError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 190001 +} + +// IsStatsError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 200001 +// - HTTP code: 400 +// - message: "Stats error: %s" +func IsStatsError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 200001 +} + +// IsStatsUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 200002 +// - HTTP code: 503 +// - message: "Stats unavailable: %s" +func IsStatsUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 200002 +} + +// IsAppStoppedStatsError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 200003 +// - HTTP code: 400 +// - message: "Could not fetch stats for stopped app: %s" +func IsAppStoppedStatsError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 200003 +} + +// IsRouteInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210001 +// - HTTP code: 400 +// - message: "The route is invalid: %s" +func IsRouteInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210001 +} + +// IsRouteNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210002 +// - HTTP code: 404 +// - message: "The route could not be found: %s" +func IsRouteNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210002 +} + +// IsRouteHostTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210003 +// - HTTP code: 400 +// - message: "The host is taken: %s" +func IsRouteHostTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210003 +} + +// IsRoutePathTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210004 +// - HTTP code: 400 +// - message: "The path is taken: %s" +func IsRoutePathTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210004 +} + +// IsRoutePortTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210005 +// - HTTP code: 400 +// - message: "The port is taken: %s" +func IsRoutePortTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210005 +} + +// IsRouteMappingTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210006 +// - HTTP code: 400 +// - message: "The route mapping is taken: %s" +func IsRouteMappingTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210006 +} + +// IsRouteMappingNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210007 +// - HTTP code: 404 +// - message: "The route mapping could not be found: %s" +func IsRouteMappingNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210007 +} + +// IsRouterGroupNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 210009 +// - HTTP code: 404 +// - message: "The router group could not be found: %s" +func IsRouterGroupNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 210009 +} + +// IsInstancesError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 220001 +// - HTTP code: 400 +// - message: "Instances error: %s" +func IsInstancesError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 220001 +} + +// IsInstancesUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 220002 +// - HTTP code: 503 +// - message: "Instances information unavailable: %s" +func IsInstancesUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 220002 +} + +// IsEventNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 230002 +// - HTTP code: 404 +// - message: "Event could not be found: %s" +func IsEventNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 230002 +} + +// IsQuotaDefinitionNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 240001 +// - HTTP code: 404 +// - message: "Quota Definition could not be found: %s" +func IsQuotaDefinitionNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 240001 +} + +// IsQuotaDefinitionNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 240002 +// - HTTP code: 400 +// - message: "Quota Definition is taken: %s" +func IsQuotaDefinitionNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 240002 +} + +// IsQuotaDefinitionInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 240003 +// - HTTP code: 400 +// - message: "Quota Definition is invalid: %s" +func IsQuotaDefinitionInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 240003 +} + +// IsQuotaDefinitionMemoryLimitNegativeError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 240004 +// - HTTP code: 400 +// - message: "Quota Definition memory limit cannot be negative" +func IsQuotaDefinitionMemoryLimitNegativeError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 240004 +} + +// IsStackInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 250001 +// - HTTP code: 400 +// - message: "The stack is invalid: %s" +func IsStackInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 250001 +} + +// IsStackNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 250002 +// - HTTP code: 400 +// - message: "The stack name is taken: %s" +func IsStackNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 250002 +} + +// IsStackNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 250003 +// - HTTP code: 404 +// - message: "The stack could not be found: %s" +func IsStackNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 250003 +} + +// IsServicePlanVisibilityInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 260001 +// - HTTP code: 400 +// - message: "Service Plan Visibility is invalid: %s" +func IsServicePlanVisibilityInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 260001 +} + +// IsServicePlanVisibilityAlreadyExistsError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 260002 +// - HTTP code: 400 +// - message: "This combination of ServicePlan and Organization is already taken: %s" +func IsServicePlanVisibilityAlreadyExistsError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 260002 +} + +// IsServicePlanVisibilityNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 260003 +// - HTTP code: 404 +// - message: "The service plan visibility could not be found: %s" +func IsServicePlanVisibilityNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 260003 +} + +// IsServiceBrokerInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270001 +// - HTTP code: 400 +// - message: "Service broker is invalid: %s" +func IsServiceBrokerInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270001 +} + +// IsServiceBrokerNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270002 +// - HTTP code: 400 +// - message: "The service broker name is taken" +func IsServiceBrokerNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270002 +} + +// IsServiceBrokerUrlTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270003 +// - HTTP code: 400 +// - message: "The service broker url is taken: %s" +func IsServiceBrokerUrlTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270003 +} + +// IsServiceBrokerNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270004 +// - HTTP code: 404 +// - message: "The service broker was not found: %s" +func IsServiceBrokerNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270004 +} + +// IsServiceBrokerNotRemovableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270010 +// - HTTP code: 400 +// - message: "Can not remove brokers that have associated service instances: %s" +func IsServiceBrokerNotRemovableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270010 +} + +// IsServiceBrokerUrlInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270011 +// - HTTP code: 400 +// - message: "%s is not a valid URL" +func IsServiceBrokerUrlInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270011 +} + +// IsServiceBrokerCatalogInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270012 +// - HTTP code: 502 +// - message: "Service broker catalog is invalid: %s" +func IsServiceBrokerCatalogInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270012 +} + +// IsServiceBrokerDashboardClientFailureError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270013 +// - HTTP code: 502 +// - message: "Service broker dashboard clients could not be modified: %s" +func IsServiceBrokerDashboardClientFailureError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270013 +} + +// IsServiceBrokerAsyncRequiredError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270014 +// - HTTP code: 400 +// - message: "This service plan requires client support for asynchronous service operations." +func IsServiceBrokerAsyncRequiredError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270014 +} + +// IsServiceDashboardClientMissingUrlError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 270015 +// - HTTP code: 502 +// - message: "Service broker returned dashboard client configuration without a dashboard URL" +func IsServiceDashboardClientMissingUrlError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 270015 +} + +// IsBuildpackNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290001 +// - HTTP code: 400 +// - message: "The buildpack name is already in use: %s" +func IsBuildpackNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290001 +} + +// IsBuildpackBitsUploadInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290002 +// - HTTP code: 400 +// - message: "The buildpack upload is invalid: %s" +func IsBuildpackBitsUploadInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290002 +} + +// IsBuildpackInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290003 +// - HTTP code: 400 +// - message: "Buildpack is invalid: %s" +func IsBuildpackInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290003 +} + +// IsCustomBuildpacksDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290004 +// - HTTP code: 400 +// - message: "Custom buildpacks are disabled" +func IsCustomBuildpacksDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290004 +} + +// IsBuildpackLockedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290005 +// - HTTP code: 409 +// - message: "The buildpack is locked" +func IsBuildpackLockedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290005 +} + +// IsJobTimeoutError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290006 +// - HTTP code: 524 +// - message: "The job execution has timed out." +func IsJobTimeoutError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290006 +} + +// IsSpaceDeleteTimeoutError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290007 +// - HTTP code: 524 +// - message: "Deletion of space %s timed out before all resources within could be deleted" +func IsSpaceDeleteTimeoutError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290007 +} + +// IsSpaceDeletionFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290008 +// - HTTP code: 502 +// - message: "Deletion of space %s failed because one or more resources within could not be deleted.\n\n%s" +func IsSpaceDeletionFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290008 +} + +// IsOrganizationDeleteTimeoutError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290009 +// - HTTP code: 524 +// - message: "Delete of organization %s timed out before all resources within could be deleted" +func IsOrganizationDeleteTimeoutError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290009 +} + +// IsOrganizationDeletionFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290010 +// - HTTP code: 502 +// - message: "Deletion of organization %s failed because one or more resources within could not be deleted.\n\n%s" +func IsOrganizationDeletionFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290010 +} + +// IsNonrecursiveSpaceDeletionFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290011 +// - HTTP code: 400 +// - message: "Resource inside space %s must first be deleted, or specify recursive delete." +func IsNonrecursiveSpaceDeletionFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290011 +} + +// IsBitsServiceError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290012 +// - HTTP code: 500 +// - message: "The bits service returned an error: %s" +func IsBitsServiceError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290012 +} + +// IsSpaceRolesDeletionTimeoutError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290013 +// - HTTP code: 524 +// - message: "Deletion of roles for space %s timed out before all roles could be deleted" +func IsSpaceRolesDeletionTimeoutError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290013 +} + +// IsOrganizationRolesDeletionFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290014 +// - HTTP code: 502 +// - message: "Failed to delete one or more roles for organization %s" +func IsOrganizationRolesDeletionFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290014 +} + +// IsSpaceRolesDeletionFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 290016 +// - HTTP code: 502 +// - message: "Failed to delete one or more roles for space %s" +func IsSpaceRolesDeletionFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 290016 +} + +// IsSecurityGroupInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 300001 +// - HTTP code: 400 +// - message: "The security group is invalid: %s" +func IsSecurityGroupInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 300001 +} + +// IsSecurityGroupNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 300002 +// - HTTP code: 404 +// - message: "The security group could not be found: %s" +func IsSecurityGroupNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 300002 +} + +// IsSecurityGroupStagingDefaultInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 300003 +// - HTTP code: 400 +// - message: "The security group could not be found: %s" +func IsSecurityGroupStagingDefaultInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 300003 +} + +// IsSecurityGroupRunningDefaultInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 300004 +// - HTTP code: 400 +// - message: "The security group could not be found: %s" +func IsSecurityGroupRunningDefaultInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 300004 +} + +// IsSecurityGroupNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 300005 +// - HTTP code: 400 +// - message: "The security group name is taken: %s" +func IsSecurityGroupNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 300005 +} + +// IsSpaceQuotaDefinitionInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310001 +// - HTTP code: 400 +// - message: "Space Quota Definition is invalid: %s" +func IsSpaceQuotaDefinitionInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310001 +} + +// IsSpaceQuotaDefinitionNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310002 +// - HTTP code: 400 +// - message: "The space quota definition name is taken: %s" +func IsSpaceQuotaDefinitionNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310002 +} + +// IsSpaceQuotaMemoryLimitExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310003 +// - HTTP code: 400 +// - message: "You have exceeded your space's memory limit: %s" +func IsSpaceQuotaMemoryLimitExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310003 +} + +// IsSpaceQuotaInstanceMemoryLimitExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310004 +// - HTTP code: 400 +// - message: "You have exceeded the instance memory limit for your space's quota." +func IsSpaceQuotaInstanceMemoryLimitExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310004 +} + +// IsSpaceQuotaTotalRoutesExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310005 +// - HTTP code: 400 +// - message: "You have exceeded the total routes for your space's quota." +func IsSpaceQuotaTotalRoutesExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310005 +} + +// IsOrgQuotaTotalRoutesExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310006 +// - HTTP code: 400 +// - message: "You have exceeded the total routes for your organization's quota." +func IsOrgQuotaTotalRoutesExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310006 +} + +// IsSpaceQuotaDefinitionNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310007 +// - HTTP code: 404 +// - message: "Space Quota Definition could not be found: %s" +func IsSpaceQuotaDefinitionNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310007 +} + +// IsSpaceQuotaInstanceLimitExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310008 +// - HTTP code: 400 +// - message: "You have exceeded the instance limit for your space's quota." +func IsSpaceQuotaInstanceLimitExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310008 +} + +// IsOrgQuotaTotalReservedRoutePortsExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310009 +// - HTTP code: 400 +// - message: "You have exceeded the total reserved route ports for your organization's quota." +func IsOrgQuotaTotalReservedRoutePortsExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310009 +} + +// IsSpaceQuotaTotalReservedRoutePortsExceededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 310010 +// - HTTP code: 400 +// - message: "You have exceeded the total reserved route ports for your space's quota." +func IsSpaceQuotaTotalReservedRoutePortsExceededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 310010 +} + +// IsDiegoDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 320001 +// - HTTP code: 400 +// - message: "Diego has not been enabled." +func IsDiegoDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 320001 +} + +// IsDiegoDockerBuildpackConflictError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 320002 +// - HTTP code: 400 +// - message: "You cannot specify a custom buildpack and a docker image at the same time." +func IsDiegoDockerBuildpackConflictError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 320002 +} + +// IsDockerDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 320003 +// - HTTP code: 400 +// - message: "Docker support has not been enabled." +func IsDockerDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 320003 +} + +// IsStagingBackendInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 320004 +// - HTTP code: 403 +// - message: "The request staging completion endpoint only handles apps desired to stage on the Diego backend." +func IsStagingBackendInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 320004 +} + +// IsBackendSelectionNotAuthorizedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 320005 +// - HTTP code: 403 +// - message: "You cannot select the backend on which to run this application" +func IsBackendSelectionNotAuthorizedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 320005 +} + +// IsFeatureFlagNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 330000 +// - HTTP code: 404 +// - message: "The feature flag could not be found: %s" +func IsFeatureFlagNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 330000 +} + +// IsFeatureFlagInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 330001 +// - HTTP code: 400 +// - message: "The feature flag is invalid: %s" +func IsFeatureFlagInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 330001 +} + +// IsFeatureDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 330002 +// - HTTP code: 403 +// - message: "Feature Disabled: %s" +func IsFeatureDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 330002 +} + +// IsUserProvidedServiceInstanceNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 340001 +// - HTTP code: 404 +// - message: "The service instance could not be found: %s" +func IsUserProvidedServiceInstanceNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 340001 +} + +// IsUserProvidedServiceInstanceHandlerNeededError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 340002 +// - HTTP code: 400 +// - message: "Please use the User Provided Services API to manage this resource." +func IsUserProvidedServiceInstanceHandlerNeededError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 340002 +} + +// IsProcessInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 350001 +// - HTTP code: 400 +// - message: "The process is invalid: %s" +func IsProcessInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 350001 +} + +// IsUnableToDeleteError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 350002 +// - HTTP code: 400 +// - message: "Unable to perform delete action: %s" +func IsUnableToDeleteError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 350002 +} + +// IsProcessNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 350003 +// - HTTP code: 404 +// - message: "The process could not be found: %s" +func IsProcessNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 350003 +} + +// IsServiceKeyNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 360001 +// - HTTP code: 400 +// - message: "The service key name is taken: %s" +func IsServiceKeyNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 360001 +} + +// IsServiceKeyInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 360002 +// - HTTP code: 400 +// - message: "The service key is invalid: %s" +func IsServiceKeyInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 360002 +} + +// IsServiceKeyNotFoundError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 360003 +// - HTTP code: 404 +// - message: "The service key could not be found: %s" +func IsServiceKeyNotFoundError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 360003 +} + +// IsServiceKeyNotSupportedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 360004 +// - HTTP code: 400 +// - message: "%s" +func IsServiceKeyNotSupportedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 360004 +} + +// IsServiceKeyCredentialStoreUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 360005 +// - HTTP code: 503 +// - message: "Credential store is unavailable" +func IsServiceKeyCredentialStoreUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 360005 +} + +// IsRoutingApiUnavailableError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 370001 +// - HTTP code: 503 +// - message: "The Routing API is currently unavailable" +func IsRoutingApiUnavailableError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 370001 +} + +// IsRoutingApiDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 370003 +// - HTTP code: 403 +// - message: "Routing API is disabled" +func IsRoutingApiDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 370003 +} + +// IsEnvironmentVariableGroupInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 380001 +// - HTTP code: 400 +// - message: "The Environment Variable Group is invalid: %s" +func IsEnvironmentVariableGroupInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 380001 +} + +// IsDropletUploadInvalidError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 380002 +// - HTTP code: 400 +// - message: "The droplet upload is invalid: %s" +func IsDropletUploadInvalidError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 380002 +} + +// IsServiceInstanceUnshareFailedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 390001 +// - HTTP code: 502 +// - message: "Unshare of service instance failed because one or more bindings could not be deleted.\n\n%s" +func IsServiceInstanceUnshareFailedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 390001 +} + +// IsServiceInstanceDeletionSharesExistsError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 390002 +// - HTTP code: 400 +// - message: "Service instances must be unshared before they can be deleted. Unsharing %s will automatically delete any bindings that have been made to applications in other spaces." +func IsServiceInstanceDeletionSharesExistsError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 390002 +} + +// IsServiceShareIsDisabledError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 390003 +// - HTTP code: 400 +// - message: "The %s service does not support service instance sharing." +func IsServiceShareIsDisabledError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 390003 +} + +// IsUserProvidedServiceInstanceSharingNotSupportedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 390004 +// - HTTP code: 400 +// - message: "User-provided services cannot be shared" +func IsUserProvidedServiceInstanceSharingNotSupportedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 390004 +} + +// IsRouteServiceInstanceSharingNotSupportedError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 390005 +// - HTTP code: 400 +// - message: "Route services cannot be shared" +func IsRouteServiceInstanceSharingNotSupportedError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 390005 +} + +// IsSharedServiceInstanceNameTakenError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 390006 +// - HTTP code: 400 +// - message: "A service instance called %s already exists in %s" +func IsSharedServiceInstanceNameTakenError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 390006 +} + +// IsInvalidServiceInstanceSharingTargetSpaceError returns a boolean indicating whether +// the error is known to report the Cloud Foundry error: +// - Cloud Foundry code: 390007 +// - HTTP code: 422 +// - message: "Service instances cannot be shared into the space where they were created" +func IsInvalidServiceInstanceSharingTargetSpaceError(err error) bool { + cause := errors.Cause(err) + cferr, ok := cause.(CloudFoundryError) + if !ok { + return false + } + return cferr.Code == 390007 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/client.go b/vendor/github.com/cloudfoundry-community/go-cfclient/client.go new file mode 100644 index 0000000000..16bc10996c --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/client.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/domains.go b/vendor/github.com/cloudfoundry-community/go-cfclient/domains.go new file mode 100644 index 0000000000..d779b0a37b --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/domains.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/environmentvariablegroups.go b/vendor/github.com/cloudfoundry-community/go-cfclient/environmentvariablegroups.go new file mode 100644 index 0000000000..87b9426072 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/environmentvariablegroups.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/error.go b/vendor/github.com/cloudfoundry-community/go-cfclient/error.go new file mode 100644 index 0000000000..e4decdf88b --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/error.go @@ -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) +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/events.go b/vendor/github.com/cloudfoundry-community/go-cfclient/events.go new file mode 100644 index 0000000000..dffdd7a595 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/events.go @@ -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) +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/gen_error.go b/vendor/github.com/cloudfoundry-community/go-cfclient/gen_error.go new file mode 100644 index 0000000000..7405c6f3e7 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/gen_error.go @@ -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 }} +`)) diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/go.mod b/vendor/github.com/cloudfoundry-community/go-cfclient/go.mod new file mode 100644 index 0000000000..5404ca7cac --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/go.mod @@ -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 +) diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/go.sum b/vendor/github.com/cloudfoundry-community/go-cfclient/go.sum new file mode 100644 index 0000000000..945c3858ba --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/go.sum @@ -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= diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/info.go b/vendor/github.com/cloudfoundry-community/go-cfclient/info.go new file mode 100644 index 0000000000..4f14ac582d --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/info.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/isolationsegments.go b/vendor/github.com/cloudfoundry-community/go-cfclient/isolationsegments.go new file mode 100644 index 0000000000..2be40e5572 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/isolationsegments.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/org_quotas.go b/vendor/github.com/cloudfoundry-community/go-cfclient/org_quotas.go new file mode 100644 index 0000000000..ea935e1602 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/org_quotas.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/orgs.go b/vendor/github.com/cloudfoundry-community/go-cfclient/orgs.go new file mode 100644 index 0000000000..4c71b94f9e --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/orgs.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/processes.go b/vendor/github.com/cloudfoundry-community/go-cfclient/processes.go new file mode 100644 index 0000000000..bfdb804c0f --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/processes.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/route_mappings.go b/vendor/github.com/cloudfoundry-community/go-cfclient/route_mappings.go new file mode 100644 index 0000000000..d422d496be --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/route_mappings.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/routes.go b/vendor/github.com/cloudfoundry-community/go-cfclient/routes.go new file mode 100644 index 0000000000..dfd75f84bc --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/routes.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/secgroups.go b/vendor/github.com/cloudfoundry-community/go-cfclient/secgroups.go new file mode 100644 index 0000000000..bcc827bbf8 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/secgroups.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/service_bindings.go b/vendor/github.com/cloudfoundry-community/go-cfclient/service_bindings.go new file mode 100644 index 0000000000..09bdecc8a3 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/service_bindings.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/service_brokers.go b/vendor/github.com/cloudfoundry-community/go-cfclient/service_brokers.go new file mode 100644 index 0000000000..4cc5b28e44 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/service_brokers.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/service_instances.go b/vendor/github.com/cloudfoundry-community/go-cfclient/service_instances.go new file mode 100644 index 0000000000..129b313819 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/service_instances.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/service_keys.go b/vendor/github.com/cloudfoundry-community/go-cfclient/service_keys.go new file mode 100644 index 0000000000..3c13a03509 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/service_keys.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/service_plan_visibilities.go b/vendor/github.com/cloudfoundry-community/go-cfclient/service_plan_visibilities.go new file mode 100644 index 0000000000..d4a84579f6 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/service_plan_visibilities.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/service_plans.go b/vendor/github.com/cloudfoundry-community/go-cfclient/service_plans.go new file mode 100644 index 0000000000..a260359492 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/service_plans.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/service_usage_events.go b/vendor/github.com/cloudfoundry-community/go-cfclient/service_usage_events.go new file mode 100644 index 0000000000..17fb8a2b1e --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/service_usage_events.go @@ -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) +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/services.go b/vendor/github.com/cloudfoundry-community/go-cfclient/services.go new file mode 100644 index 0000000000..b02365b946 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/services.go @@ -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) +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/space_quotas.go b/vendor/github.com/cloudfoundry-community/go-cfclient/space_quotas.go new file mode 100644 index 0000000000..6c82a062e1 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/space_quotas.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/spaces.go b/vendor/github.com/cloudfoundry-community/go-cfclient/spaces.go new file mode 100644 index 0000000000..be79a3a4f8 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/spaces.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/stacks.go b/vendor/github.com/cloudfoundry-community/go-cfclient/stacks.go new file mode 100644 index 0000000000..23124c0003 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/stacks.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/tasks.go b/vendor/github.com/cloudfoundry-community/go-cfclient/tasks.go new file mode 100644 index 0000000000..cce27ef36f --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/tasks.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/types.go b/vendor/github.com/cloudfoundry-community/go-cfclient/types.go new file mode 100644 index 0000000000..279106bfa4 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/types.go @@ -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"` +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/user_provided_service_instances.go b/vendor/github.com/cloudfoundry-community/go-cfclient/user_provided_service_instances.go new file mode 100644 index 0000000000..221057ac2b --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/user_provided_service_instances.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/users.go b/vendor/github.com/cloudfoundry-community/go-cfclient/users.go new file mode 100644 index 0000000000..b0ddc10c7e --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/users.go @@ -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 +} diff --git a/vendor/github.com/cloudfoundry-community/go-cfclient/v3types.go b/vendor/github.com/cloudfoundry-community/go-cfclient/v3types.go new file mode 100644 index 0000000000..7028f16743 --- /dev/null +++ b/vendor/github.com/cloudfoundry-community/go-cfclient/v3types.go @@ -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"` +} diff --git a/vendor/github.com/hashicorp/go-hclog/context.go b/vendor/github.com/hashicorp/go-hclog/context.go new file mode 100644 index 0000000000..7815f50194 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/context.go @@ -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{} diff --git a/vendor/github.com/hashicorp/go-hclog/global.go b/vendor/github.com/hashicorp/go-hclog/global.go index e5f7f95ff0..3efc54c129 100644 --- a/vendor/github.com/hashicorp/go-hclog/global.go +++ b/vendor/github.com/hashicorp/go-hclog/global.go @@ -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 +} diff --git a/vendor/github.com/hashicorp/go-hclog/intlogger.go b/vendor/github.com/hashicorp/go-hclog/intlogger.go index d32630c29c..219656c4cb 100644 --- a/vendor/github.com/hashicorp/go-hclog/intlogger.go +++ b/vendor/github.com/hashicorp/go-hclog/intlogger.go @@ -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, + } } diff --git a/vendor/github.com/hashicorp/go-hclog/logger.go b/vendor/github.com/hashicorp/go-hclog/logger.go index 0f1f8264cc..080ed79996 100644 --- a/vendor/github.com/hashicorp/go-hclog/logger.go +++ b/vendor/github.com/hashicorp/go-hclog/logger.go @@ -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. diff --git a/vendor/github.com/hashicorp/go-hclog/stdlog.go b/vendor/github.com/hashicorp/go-hclog/stdlog.go index 913d523b57..044a469608 100644 --- a/vendor/github.com/hashicorp/go-hclog/stdlog.go +++ b/vendor/github.com/hashicorp/go-hclog/stdlog.go @@ -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: diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/.gitignore b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/.gitignore new file mode 100644 index 0000000000..2ee43a22a9 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/.gitignore @@ -0,0 +1,25 @@ +# 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* + +# Ignore fake certificates generated for tests +testdata/fake-certificates* diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/LICENSE b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/LICENSE new file mode 100644 index 0000000000..a612ad9813 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/LICENSE @@ -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. diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/Makefile b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/Makefile new file mode 100644 index 0000000000..c64242fcc0 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/Makefile @@ -0,0 +1,60 @@ +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: gencerts 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'" + +gencerts: + @sh -c "'$(CURDIR)/scripts/generate-test-certs.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 \ No newline at end of file diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/README.md b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/README.md new file mode 100644 index 0000000000..28d37bc753 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/README.md @@ -0,0 +1,228 @@ +# 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. + +## Getting Started + +- `$ git clone git@github.com:hashicorp/vault-plugin-auth-pcf.git` +- `$ cd vault-plugin-auth-pcf` +- `$ PCF_HOME=$(pwd)` +- `$ make test` +- `$ make tools` + +`$ make test` is run above to generate valid fake certificates in your `testdata/fake-certificates` folder. +`$ make tools` is run above to install a number of tools that have been placed here in the `cmd` directory +to make your life easier. Running the command will place them in your `$GOPATH/bin` directory. + +## 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 vault-plugin-auth-pcf +``` + +Next, configure the plugin. In the `config` call below, the `certificates` configured is intended to be the CA +certificate that has been configured as the `diego.executor.instance_identity_ca_cert` in your environment. For +instructions on configuring this, see PCF's +[Enabling Instance Identity](https://docs.cloudfoundry.org/adminguide/instance-identity.html). + +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/vault-plugin-auth-pcf/config \ + certificates=@$PCF_HOME/testdata/fake-certificates/ca.crt \ + pcf_api_addr=http://127.0.0.1:33671 \ + pcf_username=username \ + pcf_password=password +``` + +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/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 \ + ttl=86400s \ + max_ttl=86400s \ + period=86400s +``` + +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 +$ 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) +``` + +### 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 + +### 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. In the CF Dev environment, +it can be obtained via `$ bosh int --path /diego_instance_identity_ca ~/.cfdev/state/bosh/creds.yml`. In a prod +environment, it should be available through the Ops Manager API. + +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 + +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" +``` \ No newline at end of file diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/backend.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/backend.go new file mode 100644 index 0000000000..d6920a39e9 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/backend.go @@ -0,0 +1,52 @@ +package pcf + +import ( + "context" + "github.com/hashicorp/go-hclog" + "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{ + logger: hclog.Default(), + } + 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 + logger hclog.Logger +} + +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.' +` diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/cli.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/cli.go new file mode 100644 index 0000000000..b0a91ed721 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/cli.go @@ -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 + } + certificate := string(certBytes) + + signingTime := time.Now().UTC() + signatureData := &signatures.SignatureData{ + SigningTime: signingTime, + Role: role, + Certificate: certificate, + } + signature, err := signatures.Sign(pathToInstanceKey, signatureData) + if err != nil { + return nil, err + } + + loginData := map[string]interface{}{ + "role": role, + "certificate": certificate, + "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= + Explicit value to use for the path to the PCF instance certificate. + + cf_instance_key= + Explicit value to use for the path to the PCF instance key. + + mount= + 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= + Name of the role to request a token against +` + + return strings.TrimSpace(help) +} diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.mod b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.mod new file mode 100644 index 0000000000..a969ad8cdd --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.mod @@ -0,0 +1,14 @@ +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-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 +) diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.sum b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.sum new file mode 100644 index 0000000000..35636f02ba --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/go.sum @@ -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= diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/configuration.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/configuration.go new file mode 100644 index 0000000000..ee78bf0036 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/configuration.go @@ -0,0 +1,56 @@ +package models + +import ( + "crypto/x509" + "errors" + "fmt" +) + +// NewConfiguration is the way a Configuration is intended to be obtained. It ensures the +// given certificates are valid and prepares a CA certificate pool to be used for client +// certificate verification. +func NewConfiguration(certificates []string, pcfAPIAddr, pcfUsername, pcfPassword string) (*Configuration, error) { + config := &Configuration{ + Certificates: certificates, + PCFAPIAddr: pcfAPIAddr, + PCFUsername: pcfUsername, + PCFPassword: pcfPassword, + } + pool := x509.NewCertPool() + for _, certificate := range certificates { + if ok := pool.AppendCertsFromPEM([]byte(certificate)); !ok { + return nil, fmt.Errorf("couldn't append CA certificate: %s", certificate) + } + } + config.verifyOpts = &x509.VerifyOptions{Roots: pool} + return config, nil +} + +// Configuration is not intended to by directly instantiated; please use NewConfiguration. +type Configuration struct { + // Certificates are the CA certificates that should be used for verifying client certificates. + Certificates []string `json:"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"` + + // verifyOpts is intentionally lower-cased so it won't be stored in JSON. + // Instead, this struct is expected to be created from NewConfiguration + // so that it'll populate this field. + verifyOpts *x509.VerifyOptions +} + +// VerifyOpts returns the options that can be used for verifying client certificates, +// including the CA certificate pool. +func (c *Configuration) VerifyOpts() (x509.VerifyOptions, error) { + if c.verifyOpts == nil { + return x509.VerifyOptions{}, errors.New("verify options are unset") + } + return *c.verifyOpts, nil +} diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/pcf_cert.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/pcf_cert.go new file mode 100644 index 0000000000..de5f9aa045 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/pcf_cert.go @@ -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 +} diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/role.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/role.go new file mode 100644 index 0000000000..6cfd0e27e9 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/models/role.go @@ -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"` +} diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_config.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_config.go new file mode 100644 index 0000000000..10e715939a --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_config.go @@ -0,0 +1,215 @@ +package pcf + +import ( + "context" + "fmt" + "strings" + + "github.com/cloudfoundry-community/go-cfclient" + "github.com/hashicorp/vault-plugin-auth-pcf/models" + "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{ + "certificates": { + Required: true, + Type: framework.TypeStringSlice, + Description: "The PEM-format CA certificates.", + }, + "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, + }, + }, + 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. + certificates := data.Get("certificates").([]string) + if len(certificates) == 0 { + return logical.ErrorResponse("'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 + } + config, err = models.NewConfiguration(certificates, pcfApiAddr, pcfUsername, pcfPassword) + if err != nil { + return logical.ErrorResponse(err.Error()), nil + } + } else { + // They're updating a config. Only update the fields that have been sent in the call. + if raw, ok := data.GetOk("certificates"); ok { + switch v := raw.(type) { + case []interface{}: + certificates := make([]string, len(v)) + for _, certificateIfc := range v { + certificate, ok := certificateIfc.(string) + if !ok { + continue + } + certificates = append(certificates, certificate) + } + config.Certificates = certificates + case string: + config.Certificates = []string{v} + } + } + 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) + } + } + + // 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 NewClient + // method. + client, err := cfclient.NewClient(&cfclient.Config{ + ApiAddress: config.PCFAPIAddr, + Username: config.PCFUsername, + Password: config.PCFPassword, + }) + 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{}{ + "certificates": config.Certificates, + "pcf_api_addr": config.PCFAPIAddr, + "pcf_username": config.PCFUsername, + }, + }, 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 + } + configMap := make(map[string]interface{}) + if err := entry.DecodeJSON(&configMap); err != nil { + return nil, err + } + var certificates []string + certificatesIfc := configMap["certificates"].([]interface{}) + for _, certificateIfc := range certificatesIfc { + certificates = append(certificates, certificateIfc.(string)) + } + config, err := models.NewConfiguration( + certificates, + configMap["pcf_api_addr"].(string), + configMap["pcf_username"].(string), + configMap["pcf_password"].(string), + ) + if 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. +` diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_login.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_login.go new file mode 100644 index 0000000000..2353e75e03 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_login.go @@ -0,0 +1,396 @@ +package pcf + +import ( + "context" + "fmt" + "github.com/hashicorp/vault/sdk/helper/strutil" + "net" + "strings" + "time" + + "github.com/cloudfoundry-community/go-cfclient" + "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/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.", + }, + "certificate": { + Required: true, + Type: framework.TypeString, + DisplayName: "Client Certificate", + Description: "The full client certificate 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 a client certificate that should have been issued by the pre-configured +// Certificate Authority, and a signature that should have been signed by the client 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 + } + + clientCertificate := data.Get("certificate").(string) + if clientCertificate == "" { + return logical.ErrorResponse("'certificate' 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 + } + + // Ensure the time it was signed is no more than 5 minutes in the past + // or 30 seconds in the future. This is a guard against some replay attacks. + fiveMinutesAgo := timeReceived.Add(time.Minute * time.Duration(-5)) + thirtySecondsFromNow := timeReceived.Add(time.Second * time.Duration(30)) + if signingTime.Before(fiveMinutesAgo) { + return logical.ErrorResponse(fmt.Sprintf("request is too old; signed at %s but received request at %s; raw signing time is %s", signingTime, timeReceived, signingTimeRaw)), nil + } + if signingTime.After(thirtySecondsFromNow) { + return logical.ErrorResponse(fmt.Sprintf("request is too far in the future; signed at %s but received request at %s; raw signing time is %s", signingTime, timeReceived, signingTimeRaw)), nil + } + + // Ensure the private key used to create the signature matches our client + // certificate, and that it signed the same data as is presented in the body. + // This offers some protection against MITM attacks. + matchingCert, err := signatures.Verify(signature, &signatures.SignatureData{ + SigningTime: signingTime, + Role: roleName, + Certificate: clientCertificate, + }) + if err != nil { + return logical.ErrorResponse(err.Error()), nil + } + + // Ensure the matching certificate was actually issued by the CA configured. + // This protects against self-generated client certificates. + 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") + } + verifyOpts, err := config.VerifyOpts() + if err != nil { + return nil, err + } + if _, err := matchingCert.Verify(verifyOpts); err != nil { + return logical.ErrorResponse(err.Error()), nil + } + + // Read PCF's identity fields from the certificate. + pcfCert, err := models.NewPCFCertificateFromx509(matchingCert) + if err != nil { + return nil, err + } + + // 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 := cfclient.NewClient(&cfclient.Config{ + ApiAddress: config.PCFAPIAddr, + Username: config.PCFUsername, + Password: config.PCFPassword, + }) + if err != nil { + return err + } + + // Check everything we can using the instance ID. + serviceInstance, err := client.GetServiceInstanceByGuid(pcfCert.InstanceID) + if err != nil { + return err + } + if serviceInstance.Guid != pcfCert.InstanceID { + return fmt.Errorf("cert instance ID %s doesn't match API's expected one of %s", pcfCert.InstanceID, serviceInstance.Guid) + } + if serviceInstance.SpaceGuid != pcfCert.SpaceID { + return fmt.Errorf("cert space ID %s doesn't match API's expected one of %s", pcfCert.SpaceID, serviceInstance.SpaceGuid) + } + + // 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. +` diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_roles.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_roles.go new file mode 100644 index 0000000000..1d21d0d755 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/path_roles.go @@ -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". +` diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/signatures/version1.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/signatures/version1.go new file mode 100644 index 0000000000..8b733c1e52 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/signatures/version1.go @@ -0,0 +1,137 @@ +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 + Certificate 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.Certificate, 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 one of the private keys +// matching one of the given client certificates. It is possible for a client +// certificate string given by PCF to contain multiple certificates within its +// body, hence the looping. The matching certificate is returned and should be +// further checked to ensure it contains the app, space, and org ID, and CN; +// otherwise it would be possible to match against an injected client certificate +// to gain authentication. +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 + } + + certBytes := []byte(signatureData.Certificate) + var block *pem.Block + var result error + for { + block, certBytes = pem.Decode(certBytes) + if block == nil { + break + } + clientCerts, err := x509.ParseCertificates(block.Bytes) + if err != nil { + result = multierror.Append(result, err) + continue + } + for _, clientCert := range clientCerts { + publicKey, ok := clientCert.PublicKey.(*rsa.PublicKey) + if !ok { + result = multierror.Append(result, fmt.Errorf("not an rsa public key, it's a %t", clientCert.PublicKey)) + continue + } + + if err := rsa.VerifyPSS(publicKey, crypto.SHA256, signatureData.hash(), signatureBytes, nil); err != nil { + result = multierror.Append(result, err) + continue + } + // Success + return clientCert, nil + } + } + if result == nil { + return nil, fmt.Errorf("no matching client certificate found for %s in %s", signature, signatureData.Certificate) + } + return nil, result +} + +func IsIssuer(pathToCACert string, clientCert *x509.Certificate) (bool, error) { + caCertBytes, err := ioutil.ReadFile(pathToCACert) + if err != nil { + return false, err + } + + pool := x509.NewCertPool() + if ok := pool.AppendCertsFromPEM(caCertBytes); !ok { + return false, errors.New("couldn't append CA certificates") + } + + verifyOpts := x509.VerifyOptions{ + Roots: pool, + } + + if _, err := clientCert.Verify(verifyOpts); err != nil { + return false, err + } + // Success + return true, nil +} diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go new file mode 100644 index 0000000000..08de2f80f0 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/certificates/generate.go @@ -0,0 +1,237 @@ +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) { + caPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return "", "", "", err + } + 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 * 180), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(caPrivateKey), caPrivateKey) + if err != nil { + return "", "", "", err + } + out := &bytes.Buffer{} + pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + caCert = out.String() + out.Reset() + + block, certBytes := pem.Decode([]byte(caCert)) + if block == nil { + return "", "", "", errors.New("block shouldn't be nil") + } + if len(certBytes) > 0 { + return "", "", "", errors.New("there shouldn't be more bytes") + } + ca509cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", "", "", 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 * 180), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: false, + IPAddresses: []net.IP{net.ParseIP(ipAddress)}, + } + + clientPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return "", "", "", err + } + + derBytes, err = x509.CreateCertificate(rand.Reader, &template, ca509cert, publicKey(clientPrivateKey), caPrivateKey) + if err != nil { + return "", "", "", err + } + + pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + instanceCert = out.String() + out.Reset() + + pem.Encode(out, pemBlockForKey(clientPrivateKey)) + instanceKey = out.String() + out.Reset() + + return caCert, instanceCert, instanceKey, 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 +} diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf/mock.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf/mock.go new file mode 100644 index 0000000000..91358ace31 --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/testing/pcf/mock.go @@ -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 +}` +) diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/util.go b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/util.go new file mode 100644 index 0000000000..e05138e0cf --- /dev/null +++ b/vendor/github.com/hashicorp/vault-plugin-auth-pcf/util/util.go @@ -0,0 +1,3 @@ +package util + +const BashTimeFormat = "Mon Jan 2 15:04:05 MST 2006" diff --git a/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go new file mode 100644 index 0000000000..7a0b9ed102 --- /dev/null +++ b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go @@ -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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9b3f1816f8..baf10ea661 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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-20190605234735-619218abcd26 +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-secrets-ad v0.5.1 github.com/hashicorp/vault-plugin-secrets-ad/plugin github.com/hashicorp/vault-plugin-secrets-ad/plugin/client @@ -346,7 +359,7 @@ github.com/hashicorp/vault-plugin-secrets-gcp/plugin/util github.com/hashicorp/vault-plugin-secrets-gcpkms # github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20190416155133-fd495225dea0 github.com/hashicorp/vault-plugin-secrets-kv -# github.com/hashicorp/vault/api v1.0.1 => ./api +# github.com/hashicorp/vault/api v1.0.2 => ./api github.com/hashicorp/vault/api # github.com/hashicorp/vault/sdk v0.1.11 => ./sdk github.com/hashicorp/vault/sdk/helper/salt @@ -640,6 +653,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-20190403152447-81d4e9dc473e