From 42a84733486cc51c7d80b6c63305ef0b25747fff Mon Sep 17 00:00:00 2001 From: Danail Branekov Date: Thu, 30 Sep 2021 14:59:27 +0000 Subject: [PATCH] Detect CF-on-K8s in `cf api` This makes `cf api` able to detect the `cf-on-k8s` flag in the root endpoint response and persist this information in the config. This will allow us to enable CF-on-K8s-specific behaviour for every following command. We have introduced a new `selfcontained` integration suite which contains tests that do not need to be run against a cf deployment. Issue: cloudfoundry/cf-k8s-api#10 Co-authored-by: Danail Branekov Co-authored-by: Giuseppe Capizzi Co-authored-by: Georgi Sabev --- Makefile | 8 ++- actor/v7action/target.go | 1 + actor/v7action/target_test.go | 14 +++- api/cloudcontroller/ccv3/info.go | 3 +- api/cloudcontroller/ccv3/info_test.go | 27 +++++--- integration/README.md | 3 +- integration/v7/isolated/api_command_test.go | 2 +- .../v7/selfcontained/api_command_test.go | 66 +++++++++++++++++++ .../selfcontained/selfcontained_suite_test.go | 24 +++++++ util/configv3/json_config.go | 9 ++- util/configv3/json_config_test.go | 3 + 11 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 integration/v7/selfcontained/api_command_test.go create mode 100644 integration/v7/selfcontained/selfcontained_suite_test.go diff --git a/Makefile b/Makefile index dbb1521fe6c..b31661666ad 100644 --- a/Makefile +++ b/Makefile @@ -130,11 +130,15 @@ ip: integration-push integration-push: build integration-cleanup ## Run all push-related integration tests $(ginkgo_int) -nodes $(NODES) integration/$(TARGET)/push -integration-tests: build integration-cleanup integration-isolated integration-push integration-global ## Run all isolated, push, and global integration tests +integration-selfcontained: + $(ginkgo_int) -nodes $(NODES) integration/v7/selfcontained + +integration-tests: build integration-cleanup integration-isolated integration-push integration-global integration-selfcontained ## Run all isolated, push, selfcontained, and global integration tests + i: integration-tests-full integration-full-tests: integration-tests-full -integration-tests-full: build integration-cleanup integration-isolated integration-push integration-experimental integration-plugin integration-global ## Run all isolated, push, experimental, plugin, and global integration tests +integration-tests-full: build integration-cleanup integration-isolated integration-push integration-experimental integration-plugin integration-global integration-selfcontained # ## Run all isolated, push, experimental, plugin, selfcontained, and global integration tests integration-tests-full-ci: integration-cleanup $(ginkgo_int) -nodes $(NODES) -flakeAttempts $(FLAKE_ATTEMPTS) \ diff --git a/actor/v7action/target.go b/actor/v7action/target.go index 99ab47ba63a..586dc0f926f 100644 --- a/actor/v7action/target.go +++ b/actor/v7action/target.go @@ -31,6 +31,7 @@ func (actor Actor) SetTarget(settings TargetSettings) (Warnings, error) { Routing: rootInfo.Routing(), SkipSSLValidation: settings.SkipSSLValidation, UAA: rootInfo.UAA(), + CFOnK8s: rootInfo.CFOnK8s, }) actor.Config.SetTokenInformation("", "", "") diff --git a/actor/v7action/target_test.go b/actor/v7action/target_test.go index b09c74994a5..33e2cb0c9a1 100644 --- a/actor/v7action/target_test.go +++ b/actor/v7action/target_test.go @@ -25,7 +25,6 @@ var _ = Describe("Targeting", func() { BeforeEach(func() { actor, fakeCloudControllerClient, fakeConfig, _, _, _, _ = NewTestActor() - }) Describe("SetTarget", func() { @@ -120,6 +119,7 @@ var _ = Describe("Targeting", func() { Expect(targetInfoArgs.LogCache).To(Equal(expectedLogCache)) Expect(targetInfoArgs.Routing).To(Equal(expectedRouting)) Expect(targetInfoArgs.SkipSSLValidation).To(Equal(skipSSLValidation)) + Expect(targetInfoArgs.CFOnK8s).To(BeFalse()) }) It("clears all the token information", func() { @@ -135,6 +135,18 @@ var _ = Describe("Targeting", func() { Expect(err).ToNot(HaveOccurred()) Expect(warnings).To(ConsistOf(Warnings{"info-warning"})) }) + + When("deployed on Kubernetes", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetInfoReturns(ccv3.Info{CFOnK8s: true}, nil, nil) + }) + + It("sets the CFOnK8s target information", func() { + Expect(fakeConfig.SetTargetInformationCallCount()).To(Equal(1)) + targetInfoArgs := fakeConfig.SetTargetInformationArgsForCall(0) + Expect(targetInfoArgs.CFOnK8s).To(BeTrue()) + }) + }) }) Describe("ClearTarget", func() { diff --git a/api/cloudcontroller/ccv3/info.go b/api/cloudcontroller/ccv3/info.go index 288d1060861..8a2e694a18c 100644 --- a/api/cloudcontroller/ccv3/info.go +++ b/api/cloudcontroller/ccv3/info.go @@ -39,7 +39,8 @@ type InfoLinks struct { // controller API. type Info struct { // Links is a list of top level Cloud Controller APIs. - Links InfoLinks `json:"links"` + Links InfoLinks `json:"links"` + CFOnK8s bool `json:"cf_on_k8s"` } // AppSSHEndpoint returns the HREF for SSHing into an app container. diff --git a/api/cloudcontroller/ccv3/info_test.go b/api/cloudcontroller/ccv3/info_test.go index b90c500218f..8f77db3ba96 100644 --- a/api/cloudcontroller/ccv3/info_test.go +++ b/api/cloudcontroller/ccv3/info_test.go @@ -18,7 +18,7 @@ var _ = Describe("Info", func() { client *Client rootRespondWith http.HandlerFunc - apis Info + info Info warnings Warnings executeErr error ) @@ -37,7 +37,7 @@ var _ = Describe("Info", func() { ), ) - apis, warnings, executeErr = client.GetInfo() + info, warnings, executeErr = client.GetInfo() }) Describe("when all requests are successful", func() { @@ -86,18 +86,29 @@ var _ = Describe("Info", func() { It("returns the CC Information", func() { Expect(executeErr).NotTo(HaveOccurred()) - Expect(apis.UAA()).To(Equal("https://uaa.bosh-lite.com")) - Expect(apis.Logging()).To(Equal("wss://doppler.bosh-lite.com:443")) - Expect(apis.NetworkPolicyV1()).To(Equal(fmt.Sprintf("%s/networking/v1/external", server.URL()))) - Expect(apis.AppSSHHostKeyFingerprint()).To(Equal("some-fingerprint")) - Expect(apis.AppSSHEndpoint()).To(Equal("ssh.bosh-lite.com:2222")) - Expect(apis.OAuthClient()).To(Equal("some-client")) + Expect(info.UAA()).To(Equal("https://uaa.bosh-lite.com")) + Expect(info.Logging()).To(Equal("wss://doppler.bosh-lite.com:443")) + Expect(info.NetworkPolicyV1()).To(Equal(fmt.Sprintf("%s/networking/v1/external", server.URL()))) + Expect(info.AppSSHHostKeyFingerprint()).To(Equal("some-fingerprint")) + Expect(info.AppSSHEndpoint()).To(Equal("ssh.bosh-lite.com:2222")) + Expect(info.OAuthClient()).To(Equal("some-client")) + Expect(info.CFOnK8s).To(BeFalse()) }) It("returns all warnings", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(warnings).To(ConsistOf("warning 1")) }) + + When("CF-on-K8s", func() { + BeforeEach(func() { + rootRespondWith = RespondWith(http.StatusOK, `{ "cf_on_k8s": true }`) + }) + + It("sets the CFOnK8s", func() { + Expect(info.CFOnK8s).To(BeTrue()) + }) + }) }) When("the cloud controller encounters an error", func() { diff --git a/integration/README.md b/integration/README.md index 021f6641e3b..de326244cb7 100644 --- a/integration/README.md +++ b/integration/README.md @@ -24,7 +24,8 @@ Running `make integration-tests` can be time-consuming, because it includes the - `isolated` suite is for tests that are stand alone and do not affect each other. They are meant to run in their own organization and space, and will not affect system state. This is the most common type of integration tests. - `push` suite is for tests related to the `cf push` command only. - `experimental` suite is for tests that require the cf experimental flag to be set and/or an experimental feature for the CF CLI. -- `plugin` suite is for tests that surround the CF CLI plugin framework. *These tests do not run in parallel.* +- `plugin` suite is for tests that surround the CF CLI plugin framework. _These tests do not run in parallel._ +- `selfcontained` suite is for tests that talk to a fake CF API, hence they do not require a cf deployment ## How to run These tests rely on [ginkgo](https://github.com/onsi/ginkgo) to be installed. diff --git a/integration/v7/isolated/api_command_test.go b/integration/v7/isolated/api_command_test.go index d312c8035c9..b3339b84679 100644 --- a/integration/v7/isolated/api_command_test.go +++ b/integration/v7/isolated/api_command_test.go @@ -81,7 +81,6 @@ var _ = Describe("api command", func() { Context("--unset is passed", func() { BeforeEach(func() { - userConfig := configv3.Config{ ConfigFile: configv3.JSONConfig{ ConfigVersion: configv3.CurrentConfigVersion, @@ -338,6 +337,7 @@ var _ = Describe("api command", func() { Expect(configFile.TargetedSpace.GUID).To(BeEmpty()) Expect(configFile.TargetedSpace.Name).To(BeEmpty()) Expect(configFile.TargetedSpace.AllowSSH).To(BeFalse()) + Expect(configFile.CFOnK8s).To(Equal(configv3.CFOnK8s{})) }) It("handles API endpoints with trailing slash", func() { diff --git a/integration/v7/selfcontained/api_command_test.go b/integration/v7/selfcontained/api_command_test.go new file mode 100644 index 00000000000..77928ed22fe --- /dev/null +++ b/integration/v7/selfcontained/api_command_test.go @@ -0,0 +1,66 @@ +package selfcontained_test + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "path/filepath" + + "code.cloudfoundry.org/cli/integration/helpers" + "code.cloudfoundry.org/cli/util/configv3" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" + "github.com/onsi/gomega/ghttp" +) + +var _ = Describe("cf api", func() { + var ( + server *ghttp.Server + responseBody string + ) + + BeforeEach(func() { + responseBody = "{}" + }) + + JustBeforeEach(func() { + server = ghttp.NewServer() + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/"), + ghttp.RespondWith(http.StatusOK, responseBody), + ), + ) + + Eventually(helpers.CF("api", server.URL())).Should(gexec.Exit(0)) + }) + + AfterEach(func() { + server.Close() + }) + + It("disables cf-on-k8s in config", func() { + Expect(loadConfig().CFOnK8s.Enabled).To(BeFalse()) + }) + + When("pointed to cf-on-k8s", func() { + BeforeEach(func() { + responseBody = `{ "cf_on_k8s": true }` + }) + + It("enables cf-on-k8s in config", func() { + Expect(loadConfig().CFOnK8s.Enabled).To(BeTrue()) + }) + }) +}) + +func loadConfig() configv3.JSONConfig { + rawConfig, err := ioutil.ReadFile(filepath.Join(homeDir, ".cf", "config.json")) + Expect(err).NotTo(HaveOccurred()) + + var configFile configv3.JSONConfig + Expect(json.Unmarshal(rawConfig, &configFile)).To(Succeed()) + + return configFile +} diff --git a/integration/v7/selfcontained/selfcontained_suite_test.go b/integration/v7/selfcontained/selfcontained_suite_test.go new file mode 100644 index 00000000000..1d4b2151b34 --- /dev/null +++ b/integration/v7/selfcontained/selfcontained_suite_test.go @@ -0,0 +1,24 @@ +package selfcontained_test + +import ( + "testing" + + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var homeDir string + +func TestSelfcontained(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Selfcontained Suite") +} + +var _ = BeforeEach(func() { + homeDir = helpers.SetHomeDir() +}) + +var _ = AfterEach(func() { + helpers.DestroyHomeDir(homeDir) +}) diff --git a/util/configv3/json_config.go b/util/configv3/json_config.go index ff147958810..376b74fc6e7 100644 --- a/util/configv3/json_config.go +++ b/util/configv3/json_config.go @@ -12,6 +12,7 @@ type JSONConfig struct { APIVersion string `json:"APIVersion"` AsyncTimeout int `json:"AsyncTimeout"` AuthorizationEndpoint string `json:"AuthorizationEndpoint"` + CFOnK8s CFOnK8s `json:"CFOnK8s"` ColorEnabled string `json:"ColorEnabled"` ConfigVersion int `json:"ConfigVersion"` DopplerEndpoint string `json:"DopplerEndPoint"` @@ -48,6 +49,10 @@ type Space struct { AllowSSH bool `json:"AllowSSH"` } +type CFOnK8s struct { + Enabled bool `json:"Enabled"` +} + // User represents the user information provided by the JWT access token. type User struct { Name string @@ -192,6 +197,7 @@ type TargetInformationArgs struct { Routing string SkipSSLValidation bool UAA string + CFOnK8s bool } // SetTargetInformation sets the currently targeted CC API and related other @@ -213,6 +219,8 @@ func (config *Config) SetTargetInformation(args TargetInformationArgs) { // ever read from there. config.ConfigFile.AuthorizationEndpoint = args.Auth + config.ConfigFile.CFOnK8s.Enabled = args.CFOnK8s + config.UnsetOrganizationAndSpaceInformation() } @@ -319,7 +327,6 @@ func (config *Config) UnsetUserInformation() { config.SetUAAClientCredentials(DefaultUAAOAuthClient, DefaultUAAOAuthClientSecret) config.UnsetOrganizationAndSpaceInformation() - } // V7SetSpaceInformation sets the currently targeted space. diff --git a/util/configv3/json_config_test.go b/util/configv3/json_config_test.go index 9662c878252..09d6bf51a33 100644 --- a/util/configv3/json_config_test.go +++ b/util/configv3/json_config_test.go @@ -307,6 +307,7 @@ var _ = Describe("JSONConfig", func() { LogCache: "https://log-cache.foo.com", Routing: "https://api.foo.com/routing", SkipSSLValidation: true, + CFOnK8s: true, }) Expect(config.ConfigFile.Target).To(Equal("https://api.foo.com")) @@ -323,6 +324,8 @@ var _ = Describe("JSONConfig", func() { Expect(config.ConfigFile.TargetedSpace.GUID).To(BeEmpty()) Expect(config.ConfigFile.TargetedSpace.Name).To(BeEmpty()) Expect(config.ConfigFile.TargetedSpace.AllowSSH).To(BeFalse()) + + Expect(config.ConfigFile.CFOnK8s.Enabled).To(BeTrue()) }) })