From 081f8aefeeefba463687d266a2251d3f76b3bc23 Mon Sep 17 00:00:00 2001 From: Maria Shaldybin Date: Tue, 15 Feb 2022 19:42:03 +0000 Subject: [PATCH 001/543] Update the tip for security groups When users have dynamic ASG's feature enabled they don't need to restart/restage their application for security group updates to take and effect. --- command/v7/bind_running_security_group_command.go | 4 ++-- .../v7/bind_running_security_group_command_test.go | 2 +- command/v7/bind_security_group_command.go | 4 ++-- command/v7/bind_security_group_command_test.go | 10 +++++----- command/v7/bind_staging_security_group_command.go | 4 ++-- .../v7/bind_staging_security_group_command_test.go | 2 +- command/v7/delete_security_group_command.go | 4 ++-- command/v7/unbind_running_group_command_test.go | 2 +- .../v7/unbind_running_security_group_command.go | 4 ++-- command/v7/unbind_security_group.go | 6 +++--- command/v7/unbind_security_group_test.go | 4 ++-- .../v7/unbind_staging_security_group_command.go | 4 ++-- .../unbind_staging_security_group_command_test.go | 2 +- command/v7/update_security_group_command.go | 4 ++-- doc/adr/0007-integration-vs-command-test.md | 4 ++-- .../v7/global/bind_security_group_command_test.go | 14 +++++++------- .../unbind_running_security_group_command_test.go | 6 +++--- .../global/unbind_security_group_command_test.go | 10 +++++----- .../unbind_staging_security_group_command_test.go | 6 +++--- .../global/update_security_group_command_test.go | 4 ++-- .../isolated/delete_security_group_command_test.go | 6 +++--- 21 files changed, 53 insertions(+), 53 deletions(-) diff --git a/command/v7/bind_running_security_group_command.go b/command/v7/bind_running_security_group_command.go index 82aca45eb39..b2fafe89867 100644 --- a/command/v7/bind_running_security_group_command.go +++ b/command/v7/bind_running_security_group_command.go @@ -9,7 +9,7 @@ type BindRunningSecurityGroupCommand struct { BaseCommand SecurityGroup flag.SecurityGroup `positional-args:"yes"` - usage interface{} `usage:"CF_NAME bind-running-security-group SECURITY_GROUP\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME bind-running-security-group SECURITY_GROUP\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"apps, bind-security-group, bind-staging-security-group, restart, running-security-groups, security-groups"` } @@ -38,7 +38,7 @@ func (cmd BindRunningSecurityGroupCommand) Execute(args []string) error { } cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } diff --git a/command/v7/bind_running_security_group_command_test.go b/command/v7/bind_running_security_group_command_test.go index c993a862412..3f8850e9fb5 100644 --- a/command/v7/bind_running_security_group_command_test.go +++ b/command/v7/bind_running_security_group_command_test.go @@ -104,7 +104,7 @@ var _ = Describe("bind-running-security-group Command", func() { It("signals that the security group is bound and provides a helpful tip", func() { Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("globally bind security group warning")) Expect(executeErr).NotTo(HaveOccurred()) }) diff --git a/command/v7/bind_security_group_command.go b/command/v7/bind_security_group_command.go index 712eedfd7ca..4350d966eb0 100644 --- a/command/v7/bind_security_group_command.go +++ b/command/v7/bind_security_group_command.go @@ -15,7 +15,7 @@ type BindSecurityGroupCommand struct { RequiredArgs flag.BindSecurityGroupV7Args `positional-args:"yes"` Lifecycle flag.SecurityGroupLifecycle `long:"lifecycle" choice:"running" choice:"staging" default:"running" description:"Lifecycle phase the group applies to."` Space string `long:"space" description:"Space to bind the security group to. (Default: all existing spaces in org)"` - usage interface{} `usage:"CF_NAME bind-security-group SECURITY_GROUP ORG [--lifecycle (running | staging)] [--space SPACE]\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME bind-security-group SECURITY_GROUP ORG [--lifecycle (running | staging)] [--space SPACE]\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"apps, bind-running-security-group, bind-staging-security-group, restart, security-groups"` } @@ -99,7 +99,7 @@ func (cmd BindSecurityGroupCommand) Execute(args []string) error { cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") } return nil diff --git a/command/v7/bind_security_group_command_test.go b/command/v7/bind_security_group_command_test.go index b90fa78d797..17175730fbc 100644 --- a/command/v7/bind_security_group_command_test.go +++ b/command/v7/bind_security_group_command_test.go @@ -211,7 +211,7 @@ var _ = Describe("bind-security-group Command", func() { // When space is provided, Assigning statement should only be printed once Expect(testUI.Out).NotTo(Say(`Assigning running security group some-security-group to space some-space in org some-org as some-user\.\.\.`)) Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("get security group warning")) Expect(testUI.Err).To(Say("get org warning")) @@ -276,7 +276,7 @@ var _ = Describe("bind-security-group Command", func() { Expect(testUI.Out).To(Say("Assigning running security group %s to all spaces in org %s as some-user...", cmd.RequiredArgs.SecurityGroupName, cmd.RequiredArgs.OrganizationName)) Expect(testUI.Out).To(Say("No spaces in org %s.", cmd.RequiredArgs.OrganizationName)) Expect(testUI.Out).NotTo(Say("OK")) - Expect(testUI.Out).NotTo(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) //TODO Why? + Expect(testUI.Out).NotTo(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) //TODO Why? Expect(testUI.Err).To(Say("get security group warning")) Expect(testUI.Err).To(Say("get org warning")) @@ -313,7 +313,7 @@ var _ = Describe("bind-security-group Command", func() { Expect(testUI.Out).To(Say(`Assigning running security group some-security-group to spaces some-space-1, some-space-2 in org some-org as some-user\.\.\.`)) Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("get security group warning")) Expect(testUI.Err).To(Say("get org warning")) @@ -410,7 +410,7 @@ var _ = Describe("bind-security-group Command", func() { Expect(testUI.Out).To(Say(`Assigning staging security group some-security-group to space some-space in org some-org as some-user\.\.\.`)) Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("get security group warning")) Expect(testUI.Err).To(Say("get org warning")) @@ -484,7 +484,7 @@ var _ = Describe("bind-security-group Command", func() { Expect(testUI.Out).To(Say(`Assigning staging security group some-security-group to spaces some-space-1, some-space-2 in org some-org as some-user\.\.\.`)) Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("get security group warning")) Expect(testUI.Err).To(Say("get org warning")) diff --git a/command/v7/bind_staging_security_group_command.go b/command/v7/bind_staging_security_group_command.go index 306c0548d0c..0ee5a324052 100644 --- a/command/v7/bind_staging_security_group_command.go +++ b/command/v7/bind_staging_security_group_command.go @@ -9,7 +9,7 @@ type BindStagingSecurityGroupCommand struct { BaseCommand SecurityGroup flag.SecurityGroup `positional-args:"yes"` - usage interface{} `usage:"CF_NAME bind-staging-security-group SECURITY_GROUP\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME bind-staging-security-group SECURITY_GROUP\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"apps, bind-running-security-group, bind-security-group, restart, security-groups, staging-security-groups"` } @@ -38,7 +38,7 @@ func (cmd BindStagingSecurityGroupCommand) Execute(args []string) error { } cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } diff --git a/command/v7/bind_staging_security_group_command_test.go b/command/v7/bind_staging_security_group_command_test.go index c7069f91b7a..634ee60352a 100644 --- a/command/v7/bind_staging_security_group_command_test.go +++ b/command/v7/bind_staging_security_group_command_test.go @@ -104,7 +104,7 @@ var _ = Describe("bind-staging-security-group Command", func() { It("signals that the security group is bound and provides a helpful tip", func() { Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("globally bind security group warning")) Expect(executeErr).NotTo(HaveOccurred()) }) diff --git a/command/v7/delete_security_group_command.go b/command/v7/delete_security_group_command.go index cc446096cbf..6a941bcd45f 100644 --- a/command/v7/delete_security_group_command.go +++ b/command/v7/delete_security_group_command.go @@ -10,7 +10,7 @@ type DeleteSecurityGroupCommand struct { RequiredArgs flag.SecurityGroup `positional-args:"yes"` Force bool `long:"force" short:"f" description:"Force deletion without confirmation"` - usage interface{} `usage:"CF_NAME delete-security-group SECURITY_GROUP [-f]\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME delete-security-group SECURITY_GROUP [-f]\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"security-groups"` } @@ -64,7 +64,7 @@ func (cmd *DeleteSecurityGroupCommand) Execute(args []string) error { cmd.UI.DisplayOK() cmd.UI.DisplayNewline() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } diff --git a/command/v7/unbind_running_group_command_test.go b/command/v7/unbind_running_group_command_test.go index cef38fad9c6..a02066ab052 100644 --- a/command/v7/unbind_running_group_command_test.go +++ b/command/v7/unbind_running_group_command_test.go @@ -66,7 +66,7 @@ var _ = Describe("unbind-running-security-group Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(testUI.Out).To(Say("Unbinding security group %s from defaults for running as some-user...", cmd.RequiredArgs.SecurityGroup)) Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("get security group warning")) }) diff --git a/command/v7/unbind_running_security_group_command.go b/command/v7/unbind_running_security_group_command.go index 264a4ab86cd..fd5ac730f39 100644 --- a/command/v7/unbind_running_security_group_command.go +++ b/command/v7/unbind_running_security_group_command.go @@ -10,7 +10,7 @@ type UnbindRunningSecurityGroupCommand struct { BaseCommand RequiredArgs flag.SecurityGroup `positional-args:"yes"` - usage interface{} `usage:"CF_NAME unbind-running-security-group SECURITY_GROUP\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME unbind-running-security-group SECURITY_GROUP\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"apps, restart, running-security-groups, security-groups"` } @@ -45,7 +45,7 @@ func (cmd UnbindRunningSecurityGroupCommand) Execute(args []string) error { } cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } diff --git a/command/v7/unbind_security_group.go b/command/v7/unbind_security_group.go index 9bc55399cc8..e4a6b00f153 100644 --- a/command/v7/unbind_security_group.go +++ b/command/v7/unbind_security_group.go @@ -12,7 +12,7 @@ type UnbindSecurityGroupCommand struct { RequiredArgs flag.UnbindSecurityGroupV7Args `positional-args:"yes"` Lifecycle flag.SecurityGroupLifecycle `long:"lifecycle" choice:"running" choice:"staging" default:"running" description:"Lifecycle phase the group applies to"` - usage interface{} `usage:"CF_NAME unbind-security-group SECURITY_GROUP ORG SPACE [--lifecycle (running | staging)]\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME unbind-security-group SECURITY_GROUP ORG SPACE [--lifecycle (running | staging)]\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"apps, restart, security-groups"` } @@ -52,7 +52,7 @@ func (cmd UnbindSecurityGroupCommand) Execute(args []string) error { cmd.UI.DisplayWarning(err.Error()) cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } @@ -60,7 +60,7 @@ func (cmd UnbindSecurityGroupCommand) Execute(args []string) error { } cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } diff --git a/command/v7/unbind_security_group_test.go b/command/v7/unbind_security_group_test.go index 4af1a6edb30..cfeac315ed0 100644 --- a/command/v7/unbind_security_group_test.go +++ b/command/v7/unbind_security_group_test.go @@ -126,7 +126,7 @@ var _ = Describe("unbind-security-group Command", func() { Expect(lifecycle).To(Equal(constant.SecurityGroupLifecycle("some-lifecycle"))) Expect(testUI.Err).To(Say("Security group some-security-group not bound to space some-space for lifecycle phase 'some-lifecycle'.")) Expect(testUI.Out).To(Say("OK\n\n")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(executeErr).NotTo(HaveOccurred()) }) }) @@ -143,7 +143,7 @@ var _ = Describe("unbind-security-group Command", func() { It("indicates it successfully unbound the security group", func() { Expect(testUI.Out).To(Say("OK\n\n")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) }) }) diff --git a/command/v7/unbind_staging_security_group_command.go b/command/v7/unbind_staging_security_group_command.go index 3734ed62483..a82f1541f28 100644 --- a/command/v7/unbind_staging_security_group_command.go +++ b/command/v7/unbind_staging_security_group_command.go @@ -10,7 +10,7 @@ type UnbindStagingSecurityGroupCommand struct { BaseCommand RequiredArgs flag.SecurityGroup `positional-args:"yes"` - usage interface{} `usage:"CF_NAME unbind-staging-security-group SECURITY_GROUP\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME unbind-staging-security-group SECURITY_GROUP\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"apps, restart, security-groups, staging-security-groups"` } @@ -45,7 +45,7 @@ func (cmd UnbindStagingSecurityGroupCommand) Execute(args []string) error { } cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } diff --git a/command/v7/unbind_staging_security_group_command_test.go b/command/v7/unbind_staging_security_group_command_test.go index 7588628cc73..4658d8ed3ab 100644 --- a/command/v7/unbind_staging_security_group_command_test.go +++ b/command/v7/unbind_staging_security_group_command_test.go @@ -66,7 +66,7 @@ var _ = Describe("unbind-staging-security-group Command", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(testUI.Out).To(Say("Unbinding security group %s from defaults for staging as some-user...", cmd.RequiredArgs.SecurityGroup)) Expect(testUI.Out).To(Say("OK")) - Expect(testUI.Out).To(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Expect(testUI.Out).To(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Expect(testUI.Err).To(Say("get security group warning")) }) diff --git a/command/v7/update_security_group_command.go b/command/v7/update_security_group_command.go index 6fdbbd3f6e2..b24f47af389 100644 --- a/command/v7/update_security_group_command.go +++ b/command/v7/update_security_group_command.go @@ -11,7 +11,7 @@ type UpdateSecurityGroupCommand struct { BaseCommand RequiredArgs flag.SecurityGroupArgs `positional-args:"yes"` - usage interface{} `usage:"CF_NAME update-security-group SECURITY_GROUP PATH_TO_JSON_RULES_FILE\n\n The provided path can be an absolute or relative path to a file. The file should have\n a single array with JSON objects inside describing the rules. The JSON Base Object is\n omitted and only the square brackets and associated child object are required in the file.\n\n Valid json file example:\n [\n {\n \"protocol\": \"tcp\",\n \"destination\": \"10.0.11.0/24\",\n \"ports\": \"80,443\",\n \"description\": \"Allow http and https traffic from ZoneA\"\n }\n ]\n\nTIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications."` + usage interface{} `usage:"CF_NAME update-security-group SECURITY_GROUP PATH_TO_JSON_RULES_FILE\n\n The provided path can be an absolute or relative path to a file. The file should have\n a single array with JSON objects inside describing the rules. The JSON Base Object is\n omitted and only the square brackets and associated child object are required in the file.\n\n Valid json file example:\n [\n {\n \"protocol\": \"tcp\",\n \"destination\": \"10.0.11.0/24\",\n \"ports\": \"80,443\",\n \"description\": \"Allow http and https traffic from ZoneA\"\n }\n ]\n\nTIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications."` relatedCommands interface{} `related_commands:"restage, security-groups"` } @@ -44,6 +44,6 @@ func (cmd UpdateSecurityGroupCommand) Execute(args []string) error { cmd.UI.DisplayOK() cmd.UI.DisplayNewline() - cmd.UI.DisplayText("TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications.") + cmd.UI.DisplayText("TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications.") return nil } diff --git a/doc/adr/0007-integration-vs-command-test.md b/doc/adr/0007-integration-vs-command-test.md index 620b2589f70..8d7a721741d 100644 --- a/doc/adr/0007-integration-vs-command-test.md +++ b/doc/adr/0007-integration-vs-command-test.md @@ -38,7 +38,7 @@ output/functionality: > Security group my-group not bound to space my-space for lifecycle phase 'running'. > OK > -> TIP: Changes require an app restart (for running) or restage (for staging) to apply to existing applications. +> TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart (for running) or restage (for staging) to apply to existing applications. > ``` To do so, we would have to make a `BeforeEach()` that creates the scenario @@ -62,7 +62,7 @@ When("the space isn't bound to the security group in any lifecycle", func() { Eventually(session).Should(Say(`Unbinding security group %s from org %s / space %s as %s\.\.\.`, securityGroupName, orgName, spaceName, username)) Eventually(session.Err).Should(Say(`Security group %s not bound to space %s for lifecycle phase 'running'\.`, securityGroupName, spaceName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) diff --git a/integration/v7/global/bind_security_group_command_test.go b/integration/v7/global/bind_security_group_command_test.go index 10a3a859c81..cc881ecb38a 100644 --- a/integration/v7/global/bind_security_group_command_test.go +++ b/integration/v7/global/bind_security_group_command_test.go @@ -36,7 +36,7 @@ var _ = Describe("bind-security-group command", func() { Eventually(session).Should(Say(`\s+bind-security-group - Bind a security group to a particular space, or all existing spaces of an org`)) Eventually(session).Should(Say("USAGE:")) Eventually(session).Should(Say(`\s+cf bind-security-group SECURITY_GROUP ORG \[--lifecycle \(running \| staging\)\] \[--space SPACE\]`)) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say(`\s+--lifecycle Lifecycle phase the group applies to\. \(Default: running\)`)) Eventually(session).Should(Say(`\s+--space Space to bind the security group to\. \(Default: all existing spaces in org\)`)) @@ -151,7 +151,7 @@ var _ = Describe("bind-security-group command", func() { Eventually(session).Should(Say("Assigning running security group %s to all spaces in org %s as %s...", secGroupName, orgName, userName)) Eventually(session).Should(Say("No spaces in org %s.", orgName)) Consistently(session).ShouldNot(Say("OK")) - Eventually(session).ShouldNot(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).ShouldNot(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -169,7 +169,7 @@ var _ = Describe("bind-security-group command", func() { userName, _ := helpers.GetCredentials() Eventually(session).Should(Say(`Assigning running security group %s to spaces INTEGRATION-SPACE.*, INTEGRATION-SPACE.* in org %s as %s\.\.\.`, secGroupName, orgName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -181,7 +181,7 @@ var _ = Describe("bind-security-group command", func() { userName, _ := helpers.GetCredentials() Eventually(session).Should(Say(`Assigning running security group %s to space %s in org %s as %s\.\.\.`, secGroupName, spaceName1, orgName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -193,7 +193,7 @@ var _ = Describe("bind-security-group command", func() { userName, _ := helpers.GetCredentials() Eventually(session).Should(Say(`Assigning running security group %s to space %s in org %s as %s\.\.\.`, secGroupName, spaceName1, orgName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -206,7 +206,7 @@ var _ = Describe("bind-security-group command", func() { userName, _ := helpers.GetCredentials() Eventually(session).Should(Say(`Assigning staging security group %s to spaces INTEGRATION-SPACE.*, INTEGRATION-SPACE.* in org %s as %s\.\.\.`, secGroupName, orgName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -217,7 +217,7 @@ var _ = Describe("bind-security-group command", func() { userName, _ := helpers.GetCredentials() Eventually(session).Should(Say(`Assigning staging security group %s to space %s in org %s as %s\.\.\.`, secGroupName, spaceName1, orgName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) diff --git a/integration/v7/global/unbind_running_security_group_command_test.go b/integration/v7/global/unbind_running_security_group_command_test.go index 8bcdebb916d..73d3c8c9e17 100644 --- a/integration/v7/global/unbind_running_security_group_command_test.go +++ b/integration/v7/global/unbind_running_security_group_command_test.go @@ -27,7 +27,7 @@ var _ = Describe("unbind-running-security-group command", func() { Eventually(session).Should(Say(`\s+unbind-running-security-group - Unbind a security group from the set of security groups for running applications globally`)) Eventually(session).Should(Say("USAGE:")) Eventually(session).Should(Say(`\s+cf unbind-running-security-group SECURITY_GROUP`)) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Say("SEE ALSO:")) Eventually(session).Should(Say(`\s+apps, restart, running-security-groups, security-groups`)) Eventually(session).Should(Exit(0)) @@ -84,13 +84,13 @@ var _ = Describe("unbind-running-security-group command", func() { userName, _ := helpers.GetCredentials() Eventually(session).Should(Say("Unbinding security group %s from defaults for running as %s...", secGroupName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) session = helpers.CF("unbind-running-security-group", secGroupName) Eventually(session).Should(Say("Unbinding security group %s from defaults for running as %s...", secGroupName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) diff --git a/integration/v7/global/unbind_security_group_command_test.go b/integration/v7/global/unbind_security_group_command_test.go index 5c29ace218f..c42bbdc5152 100644 --- a/integration/v7/global/unbind_security_group_command_test.go +++ b/integration/v7/global/unbind_security_group_command_test.go @@ -31,7 +31,7 @@ var _ = Describe("unbind-security-group command", func() { Eventually(session).Should(Say(`\s+unbind-security-group - Unbind a security group from a space`)) Eventually(session).Should(Say("USAGE:")) Eventually(session).Should(Say(`\s+cf unbind-security-group SECURITY_GROUP ORG SPACE \[--lifecycle \(running \| staging\)\]`)) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say(`\s+--lifecycle Lifecycle phase the group applies to \(Default: running\)`)) Eventually(session).Should(Say("SEE ALSO:")) @@ -153,7 +153,7 @@ var _ = Describe("unbind-security-group command", func() { Eventually(session).Should(Say(`Unbinding running security group %s from org %s / space %s as %s\.\.\.`, securityGroupName, orgName, spaceName, username)) Eventually(session.Err).Should(Say(`Security group %s not bound to space %s for lifecycle phase 'running'\.`, securityGroupName, spaceName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -172,7 +172,7 @@ var _ = Describe("unbind-security-group command", func() { session := helpers.CF("unbind-security-group", securityGroupName, orgName, spaceName) Eventually(session).Should(Say(`Unbinding running security group %s from org %s / space %s as %s\.\.\.`, securityGroupName, orgName, spaceName, username)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -186,7 +186,7 @@ var _ = Describe("unbind-security-group command", func() { session := helpers.CF("unbind-security-group", securityGroupName, orgName, spaceName, "--lifecycle", "running") Eventually(session).Should(Say(`Unbinding running security group %s from org %s / space %s as %s\.\.\.`, securityGroupName, orgName, spaceName, username)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) @@ -248,7 +248,7 @@ var _ = Describe("unbind-security-group command", func() { session := helpers.CF("unbind-security-group", securityGroupName, orgName, spaceName, "--lifecycle", "staging") Eventually(session).Should(Say(`Unbinding staging security group %s from org %s / space %s as %s\.\.\.`, securityGroupName, orgName, spaceName, username)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) diff --git a/integration/v7/global/unbind_staging_security_group_command_test.go b/integration/v7/global/unbind_staging_security_group_command_test.go index d32c232716c..19916d0e063 100644 --- a/integration/v7/global/unbind_staging_security_group_command_test.go +++ b/integration/v7/global/unbind_staging_security_group_command_test.go @@ -27,7 +27,7 @@ var _ = Describe("unbind-staging-security-group command", func() { Eventually(session).Should(Say(`\s+unbind-staging-security-group - Unbind a security group from the set of security groups for staging applications globally`)) Eventually(session).Should(Say("USAGE:")) Eventually(session).Should(Say(`\s+cf unbind-staging-security-group SECURITY_GROUP`)) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Say("SEE ALSO:")) Eventually(session).Should(Say(`\s+apps, restart, security-groups, staging-security-groups`)) Eventually(session).Should(Exit(0)) @@ -84,13 +84,13 @@ var _ = Describe("unbind-staging-security-group command", func() { userName, _ := helpers.GetCredentials() Eventually(session).Should(Say("Unbinding security group %s from defaults for staging as %s...", secGroupName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) session = helpers.CF("unbind-staging-security-group", secGroupName) Eventually(session).Should(Say("Unbinding security group %s from defaults for staging as %s...", secGroupName, userName)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) }) }) diff --git a/integration/v7/global/update_security_group_command_test.go b/integration/v7/global/update_security_group_command_test.go index 9954cddef82..57c7a9ffb44 100644 --- a/integration/v7/global/update_security_group_command_test.go +++ b/integration/v7/global/update_security_group_command_test.go @@ -78,7 +78,7 @@ var _ = Describe("update-security-group command", func() { session := helpers.CF("update-security-group", securityGroupName, updatedRulesPath) Eventually(session).Should(Say(`Updating security group %s as %s`, securityGroupName, username)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) session = helpers.CF("security-group", securityGroupName) @@ -160,7 +160,7 @@ func displaysUpdateSecurityGroupHelpText(session *Session) { Eventually(session).Should(Say(`"description": "Allow http and https traffic from ZoneA"`)) Eventually(session).Should(Say("}")) Eventually(session).Should(Say("]")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Say("SEE ALSO:")) Eventually(session).Should(Say("restage, security-groups")) } diff --git a/integration/v7/isolated/delete_security_group_command_test.go b/integration/v7/isolated/delete_security_group_command_test.go index cfab73cda10..6cf0746204e 100644 --- a/integration/v7/isolated/delete_security_group_command_test.go +++ b/integration/v7/isolated/delete_security_group_command_test.go @@ -18,7 +18,7 @@ var _ = Describe("delete-security-group command", func() { Eventually(session).Should(Say(`\s+delete-security-group - Deletes a security group`)) Eventually(session).Should(Say("USAGE:")) Eventually(session).Should(Say(`\s+cf delete-security-group SECURITY_GROUP \[-f\]`)) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say(`\s+--force, -f\s+Force deletion without confirmation`)) Eventually(session).Should(Say("SEE ALSO:")) @@ -110,7 +110,7 @@ var _ = Describe("delete-security-group command", func() { Eventually(session).Should(Say(`Really delete the security group %s?`, securityGroupName)) Eventually(session).Should(Say("Deleting security group %s as %s", securityGroupName, username)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) Eventually(helpers.CF("security-group", securityGroupName)).Should(Exit(1)) }) @@ -179,7 +179,7 @@ var _ = Describe("delete-security-group command", func() { session := helpers.CF("delete-security-group", securityGroupName, "-f") Eventually(session).Should(Say("Deleting security group %s as %s", securityGroupName, username)) Eventually(session).Should(Say("OK")) - Eventually(session).Should(Say(`TIP: Changes require an app restart \(for running\) or restage \(for staging\) to apply to existing applications.`)) + Eventually(session).Should(Say(`TIP: If Dynamic ASG's are enabled, changes will automatically apply for running and staging applications. Otherwise, changes will require an app restart \(for running\) or restage \(for staging\) to apply to existing applications\.`)) Eventually(session).Should(Exit(0)) Eventually(helpers.CF("security-group", securityGroupName)).Should(Exit(1)) }) From 5e0d2bd257278514ab63bd2bb8b1be4eef3500ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Jos=C3=A9=20Calder=C3=B3n=20Pach=C3=B3n?= Date: Wed, 16 Feb 2022 15:26:31 -0500 Subject: [PATCH 002/543] Update CONTRIBUTING.md --- .github/CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8ff51f43335..fcdfadb538d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -8,7 +8,8 @@ mailing list. ## CLI Versions The cf CLI follows a branching model: -- V8 of the cf CLI is built from the [master branch](https://github.com/cloudfoundry/cli/tree/master). **This branch is under active development**. +- V9 (Next major release) of the cf CLI is built from the [master branch](https://github.com/cloudfoundry/cli/tree/master). **This branch is under active development**. +- V8 of the cf CLI is built from the [v8 branch](https://github.com/cloudfoundry/cli/tree/v8). **This branch is under active development**. - V7 of the cf CLI is built from the [v7 branch](https://github.com/cloudfoundry/cli/tree/v7). **This branch is maintenance only** and will only be updated to patch CVEs and very severe blocking defects. - V6 of the cf CLI is built from the [v6 branch](https://github.com/cloudfoundry/cli/tree/v6). **This branch is maintenance only** and will only be updated to patch CVEs and very severe blocking defects. From 522fc9a18e5afae3c85e453eb8ed7dc05aa645c0 Mon Sep 17 00:00:00 2001 From: Hector J Calderon Date: Wed, 16 Feb 2022 16:04:06 -0500 Subject: [PATCH 003/543] [Feature] Upgrade Golang version from 1.16 to 1.17 Some high security issues have been solved in the newer versions of go. Moreover, there are some security enhancements coming from 1.17. Co-authored-by: Shwetha Gururaj --- go.mod | 70 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 4733b4ee0d6..fd09459e347 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module code.cloudfoundry.org/cli -go 1.16 +go 1.17 require ( code.cloudfoundry.org/bytefmt v0.0.0-20170428003108-f4415fafc561 @@ -16,49 +16,89 @@ require ( code.cloudfoundry.org/lager v1.1.1-0.20191008172124-a9afc05ee5be code.cloudfoundry.org/tlsconfig v0.0.0-20210615191307-5d92ef3894a7 code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d - github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2 github.com/blang/semver v3.5.1+incompatible - github.com/bmatcuk/doublestar v1.3.1 // indirect - github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1 // indirect github.com/cloudfoundry/bosh-cli v5.5.1+incompatible - github.com/cloudfoundry/bosh-utils v0.0.0-20180315210917-c6a922e299b8 // indirect - github.com/cppforlife/go-patch v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.2.1 github.com/docker/distribution v2.6.0-rc.1.0.20171109224904-e5b5e44386f7+incompatible - github.com/docker/docker v1.4.2-0.20171120205147-9de84a78d76e // indirect github.com/fatih/color v1.5.1-0.20170926111411-5df930a27be2 - github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 github.com/jessevdk/go-flags v1.4.1-0.20181221193153-c0795c8afcf4 github.com/kr/pty v1.1.1 github.com/lunixbochs/vtclean v1.0.0 github.com/mattn/go-colorable v0.1.0 - github.com/mattn/go-isatty v0.0.3 // indirect github.com/mattn/go-runewidth v0.0.5-0.20181218000649-703b5e6b11ae github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2 github.com/moby/moby v1.4.2-0.20171120205147-9de84a78d76e github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 - github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/pkg/errors v0.9.1 github.com/sabhiram/go-gitignore v0.0.0-20171017070213-362f9845770f github.com/sajari/fuzzy v1.0.0 github.com/sirupsen/logrus v1.2.0 - github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00 // indirect github.com/tedsuo/rata v1.0.1-0.20170830210128-07d200713958 github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/net v0.0.0-20211013171255-e13a2654a71e - golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect golang.org/x/text v0.3.7 - golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c // indirect - google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect - google.golang.org/grpc v1.41.0 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/yaml.v2 v2.4.0 + k8s.io/apimachinery v0.22.2 k8s.io/client-go v0.22.2 ) +require ( + cloud.google.com/go v0.65.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.18 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/bmatcuk/doublestar v1.3.1 // indirect + github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 // indirect + github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1 // indirect + github.com/cloudfoundry/bosh-utils v0.0.0-20180315210917-c6a922e299b8 // indirect + github.com/cppforlife/go-patch v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/docker v1.4.2-0.20171120205147-9de84a78d76e // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/go-logr/logr v0.4.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 // indirect + github.com/imdario/mergo v0.3.5 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/mattn/go-isatty v0.0.3 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect + golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/appengine v1.6.6 // indirect + google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect + google.golang.org/grpc v1.41.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + k8s.io/klog/v2 v2.9.0 // indirect + k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) + replace gopkg.in/fsnotify.v1 v1.4.7 => github.com/fsnotify/fsnotify v1.4.7 From e44ba1986640ca3ae70fc4d634cf247acd79ad8a Mon Sep 17 00:00:00 2001 From: Hector J Calderon Date: Thu, 17 Feb 2022 15:36:49 -0500 Subject: [PATCH 004/543] [Sustaining] Updating go files with linter doing gofmt -l -w . Co-authored-by: Shwetha Gururaj --- actor/pluginaction/install_unix_test.go | 1 + actor/pluginaction/install_windows_test.go | 1 + actor/sharedaction/fix_mode_unix.go | 1 + actor/sharedaction/fix_mode_windows.go | 1 + actor/sharedaction/resource_darwin_test.go | 1 + actor/sharedaction/resource_linux_test.go | 1 + actor/sharedaction/resource_unix_test.go | 1 + actor/v7action/buildpack_unix_test.go | 1 + actor/v7action/buildpack_windows_test.go | 1 + actor/v7action/fix_mode_unix.go | 1 + actor/v7action/fix_mode_windows.go | 1 + cf/cmd/writer_unix.go | 1 + cf/cmd/writer_windows.go | 1 + cf/configuration/config_disk_persistor_unix.go | 1 + cf/configuration/config_disk_persistor_win.go | 1 + cf/net/progress_reader_test.go | 1 + cf/ssh/sigwinch/sigwinch.go | 1 + cf/ssh/sigwinch/sigwinch_win.go | 1 + cf/ssh/ssh_test.go | 1 + cf/terminal/ui_unix.go | 1 + cf/terminal/ui_windows.go | 1 + command/flag/environment_variable_test.go | 1 + integration/helpers/plugin_repo_platform_unix.go | 1 + integration/helpers/plugin_repo_platform_windows.go | 1 + integration/shared/plugin/install_plugin_command_unix_test.go | 1 + integration/shared/plugin/uninstall_plugin_command_unix_test.go | 1 + tools/tools.go | 1 + util/clissh/sigwinch/sigwinch.go | 1 + util/clissh/sigwinch/sigwinch_win.go | 1 + util/clissh/ssh_test.go | 1 + util/configv3/config_unix_test.go | 1 + util/configv3/config_windows_test.go | 1 + util/configv3/home_dir_unix.go | 1 + util/configv3/home_dir_windows.go | 1 + util/generic/executable_filename_unix.go | 1 + util/generic/executable_filename_unix_test.go | 1 + util/generic/executable_filename_windows.go | 1 + util/generic/executable_filename_windows_test.go | 1 + util/manifest/manifest_unix_test.go | 1 + util/manifest/manifest_windows_test.go | 1 + 40 files changed, 40 insertions(+) diff --git a/actor/pluginaction/install_unix_test.go b/actor/pluginaction/install_unix_test.go index 6eb7796dd1b..3c5227c5a65 100644 --- a/actor/pluginaction/install_unix_test.go +++ b/actor/pluginaction/install_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package pluginaction_test diff --git a/actor/pluginaction/install_windows_test.go b/actor/pluginaction/install_windows_test.go index 536b61932a1..f1ce037a6fd 100644 --- a/actor/pluginaction/install_windows_test.go +++ b/actor/pluginaction/install_windows_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package pluginaction_test diff --git a/actor/sharedaction/fix_mode_unix.go b/actor/sharedaction/fix_mode_unix.go index bbe7e9fc15c..680fe4cf8e8 100644 --- a/actor/sharedaction/fix_mode_unix.go +++ b/actor/sharedaction/fix_mode_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package sharedaction diff --git a/actor/sharedaction/fix_mode_windows.go b/actor/sharedaction/fix_mode_windows.go index 422a0ccb031..2e726c70782 100644 --- a/actor/sharedaction/fix_mode_windows.go +++ b/actor/sharedaction/fix_mode_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package sharedaction diff --git a/actor/sharedaction/resource_darwin_test.go b/actor/sharedaction/resource_darwin_test.go index f9e87be6eab..21444f4d461 100644 --- a/actor/sharedaction/resource_darwin_test.go +++ b/actor/sharedaction/resource_darwin_test.go @@ -1,3 +1,4 @@ +//go:build !windows && !linux // +build !windows,!linux package sharedaction_test diff --git a/actor/sharedaction/resource_linux_test.go b/actor/sharedaction/resource_linux_test.go index bc48769ae34..525a41444f0 100644 --- a/actor/sharedaction/resource_linux_test.go +++ b/actor/sharedaction/resource_linux_test.go @@ -1,3 +1,4 @@ +//go:build !windows && !darwin // +build !windows,!darwin package sharedaction_test diff --git a/actor/sharedaction/resource_unix_test.go b/actor/sharedaction/resource_unix_test.go index cfbcceb112e..4de5d12432b 100644 --- a/actor/sharedaction/resource_unix_test.go +++ b/actor/sharedaction/resource_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package sharedaction_test diff --git a/actor/v7action/buildpack_unix_test.go b/actor/v7action/buildpack_unix_test.go index 6ec78dbac64..5df6bd7c727 100644 --- a/actor/v7action/buildpack_unix_test.go +++ b/actor/v7action/buildpack_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package v7action_test diff --git a/actor/v7action/buildpack_windows_test.go b/actor/v7action/buildpack_windows_test.go index 8e1218dadb2..eb48786c536 100644 --- a/actor/v7action/buildpack_windows_test.go +++ b/actor/v7action/buildpack_windows_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package v7action_test diff --git a/actor/v7action/fix_mode_unix.go b/actor/v7action/fix_mode_unix.go index 69330b77eb9..5a3e9de0821 100644 --- a/actor/v7action/fix_mode_unix.go +++ b/actor/v7action/fix_mode_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package v7action diff --git a/actor/v7action/fix_mode_windows.go b/actor/v7action/fix_mode_windows.go index e6cd6c1256d..2ad56dedc16 100644 --- a/actor/v7action/fix_mode_windows.go +++ b/actor/v7action/fix_mode_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package v7action diff --git a/cf/cmd/writer_unix.go b/cf/cmd/writer_unix.go index bd301a68f5c..c357dbbfa86 100644 --- a/cf/cmd/writer_unix.go +++ b/cf/cmd/writer_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package cmd diff --git a/cf/cmd/writer_windows.go b/cf/cmd/writer_windows.go index 892f350bf92..2d9cab4980e 100644 --- a/cf/cmd/writer_windows.go +++ b/cf/cmd/writer_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package cmd diff --git a/cf/configuration/config_disk_persistor_unix.go b/cf/configuration/config_disk_persistor_unix.go index b1d45200ba0..bdbfb3ddf08 100644 --- a/cf/configuration/config_disk_persistor_unix.go +++ b/cf/configuration/config_disk_persistor_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package configuration diff --git a/cf/configuration/config_disk_persistor_win.go b/cf/configuration/config_disk_persistor_win.go index 859d252a858..663a7689887 100644 --- a/cf/configuration/config_disk_persistor_win.go +++ b/cf/configuration/config_disk_persistor_win.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package configuration diff --git a/cf/net/progress_reader_test.go b/cf/net/progress_reader_test.go index 9e220268991..34e37541ade 100644 --- a/cf/net/progress_reader_test.go +++ b/cf/net/progress_reader_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package net_test diff --git a/cf/ssh/sigwinch/sigwinch.go b/cf/ssh/sigwinch/sigwinch.go index ace01f9a2a2..6eab1ee5e72 100644 --- a/cf/ssh/sigwinch/sigwinch.go +++ b/cf/ssh/sigwinch/sigwinch.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package sigwinch diff --git a/cf/ssh/sigwinch/sigwinch_win.go b/cf/ssh/sigwinch/sigwinch_win.go index eac5f1ec141..045da2d6721 100644 --- a/cf/ssh/sigwinch/sigwinch_win.go +++ b/cf/ssh/sigwinch/sigwinch_win.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package sigwinch diff --git a/cf/ssh/ssh_test.go b/cf/ssh/ssh_test.go index 263a17a3530..8680011eaef 100644 --- a/cf/ssh/ssh_test.go +++ b/cf/ssh/ssh_test.go @@ -1,3 +1,4 @@ +//go:build !windows && !386 // +build !windows,!386 // skipping 386 because lager uses UInt64 in Session() diff --git a/cf/terminal/ui_unix.go b/cf/terminal/ui_unix.go index e62891cbc89..08ccdfbdd4a 100644 --- a/cf/terminal/ui_unix.go +++ b/cf/terminal/ui_unix.go @@ -1,5 +1,6 @@ // Copied from https://code.google.com/p/gopass/ +//go:build !windows // +build !windows package terminal diff --git a/cf/terminal/ui_windows.go b/cf/terminal/ui_windows.go index d964e5c00da..1dbe9574a6b 100644 --- a/cf/terminal/ui_windows.go +++ b/cf/terminal/ui_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package terminal diff --git a/command/flag/environment_variable_test.go b/command/flag/environment_variable_test.go index a4568586ce4..803316fe002 100644 --- a/command/flag/environment_variable_test.go +++ b/command/flag/environment_variable_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package flag_test diff --git a/integration/helpers/plugin_repo_platform_unix.go b/integration/helpers/plugin_repo_platform_unix.go index 97a8abf9a55..d600f7dfb9b 100644 --- a/integration/helpers/plugin_repo_platform_unix.go +++ b/integration/helpers/plugin_repo_platform_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package helpers diff --git a/integration/helpers/plugin_repo_platform_windows.go b/integration/helpers/plugin_repo_platform_windows.go index b0bdc0c1ee6..e23de538bd0 100644 --- a/integration/helpers/plugin_repo_platform_windows.go +++ b/integration/helpers/plugin_repo_platform_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package helpers diff --git a/integration/shared/plugin/install_plugin_command_unix_test.go b/integration/shared/plugin/install_plugin_command_unix_test.go index dafed2ebf9b..cca2ffeee18 100644 --- a/integration/shared/plugin/install_plugin_command_unix_test.go +++ b/integration/shared/plugin/install_plugin_command_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package plugin diff --git a/integration/shared/plugin/uninstall_plugin_command_unix_test.go b/integration/shared/plugin/uninstall_plugin_command_unix_test.go index 9ad7a6e051c..6d812450bf0 100644 --- a/integration/shared/plugin/uninstall_plugin_command_unix_test.go +++ b/integration/shared/plugin/uninstall_plugin_command_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package plugin diff --git a/tools/tools.go b/tools/tools.go index 1316cf0af2a..e6fa2b8f2e5 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -1,3 +1,4 @@ +//go:build tools // +build tools package tools diff --git a/util/clissh/sigwinch/sigwinch.go b/util/clissh/sigwinch/sigwinch.go index ace01f9a2a2..6eab1ee5e72 100644 --- a/util/clissh/sigwinch/sigwinch.go +++ b/util/clissh/sigwinch/sigwinch.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package sigwinch diff --git a/util/clissh/sigwinch/sigwinch_win.go b/util/clissh/sigwinch/sigwinch_win.go index eac5f1ec141..045da2d6721 100644 --- a/util/clissh/sigwinch/sigwinch_win.go +++ b/util/clissh/sigwinch/sigwinch_win.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package sigwinch diff --git a/util/clissh/ssh_test.go b/util/clissh/ssh_test.go index 7bd48c47dc0..c2cd5ecbffd 100644 --- a/util/clissh/ssh_test.go +++ b/util/clissh/ssh_test.go @@ -1,3 +1,4 @@ +//go:build !windows && !386 // +build !windows,!386 // skipping 386 because lager uses UInt64 in Session() diff --git a/util/configv3/config_unix_test.go b/util/configv3/config_unix_test.go index 9c790ee1522..6af4f29361b 100644 --- a/util/configv3/config_unix_test.go +++ b/util/configv3/config_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package configv3_test diff --git a/util/configv3/config_windows_test.go b/util/configv3/config_windows_test.go index 0c0a6016051..fad8241b3ac 100644 --- a/util/configv3/config_windows_test.go +++ b/util/configv3/config_windows_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package configv3_test diff --git a/util/configv3/home_dir_unix.go b/util/configv3/home_dir_unix.go index 425f34fae1a..b735d7b0797 100644 --- a/util/configv3/home_dir_unix.go +++ b/util/configv3/home_dir_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package configv3 diff --git a/util/configv3/home_dir_windows.go b/util/configv3/home_dir_windows.go index 60a5f6d3143..18b1eee6787 100644 --- a/util/configv3/home_dir_windows.go +++ b/util/configv3/home_dir_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package configv3 diff --git a/util/generic/executable_filename_unix.go b/util/generic/executable_filename_unix.go index 894053524b2..65de1273664 100644 --- a/util/generic/executable_filename_unix.go +++ b/util/generic/executable_filename_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package generic diff --git a/util/generic/executable_filename_unix_test.go b/util/generic/executable_filename_unix_test.go index 318268df5eb..84f4e3fd7db 100644 --- a/util/generic/executable_filename_unix_test.go +++ b/util/generic/executable_filename_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package generic_test diff --git a/util/generic/executable_filename_windows.go b/util/generic/executable_filename_windows.go index a0a00f4c5e5..12b39103fa5 100644 --- a/util/generic/executable_filename_windows.go +++ b/util/generic/executable_filename_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package generic diff --git a/util/generic/executable_filename_windows_test.go b/util/generic/executable_filename_windows_test.go index fe0eb03c03d..bbcb8a33577 100644 --- a/util/generic/executable_filename_windows_test.go +++ b/util/generic/executable_filename_windows_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package generic_test diff --git a/util/manifest/manifest_unix_test.go b/util/manifest/manifest_unix_test.go index 748d5cac4cb..be5775ae881 100644 --- a/util/manifest/manifest_unix_test.go +++ b/util/manifest/manifest_unix_test.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package manifest_test diff --git a/util/manifest/manifest_windows_test.go b/util/manifest/manifest_windows_test.go index 629ce11bb8c..719b4870543 100644 --- a/util/manifest/manifest_windows_test.go +++ b/util/manifest/manifest_windows_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package manifest_test From b8b0bd6fa7ca4f88fbacf43a2ae6d538129d9c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Diego=20Gonz=C3=A1lez?= <25335192+jdgonzaleza@users.noreply.github.com> Date: Tue, 8 Mar 2022 13:22:15 -0500 Subject: [PATCH 005/543] [Feature/tas-e-51] Update destinantion command (#2259) * Adds new update-destination command update destination app-protocol for a route so that the new protocol is used for all communication from and to the app using that route * Upgrades bundler version in integration go_ruby app Co-authored-by: Cristhian Pena Co-authored-by: George Gelashvili --- actor/v7action/cloud_controller_client.go | 1 + actor/v7action/route.go | 4 + actor/v7action/route_test.go | 43 ++ .../fake_cloud_controller_client.go | 82 ++++ .../ccv3/internal/api_routes.go | 2 + api/cloudcontroller/ccv3/route.go | 17 + api/cloudcontroller/ccv3/route_test.go | 87 ++++ command/common/command_list_v7.go | 1 + command/common/internal/help_all_display.go | 1 + command/v7/actor.go | 1 + command/v7/update_destination_command.go | 117 ++++++ command/v7/update_destination_command_test.go | 380 ++++++++++++++++++ command/v7/v7fakes/fake_actor.go | 82 ++++ integration/assets/go_calls_ruby/Gemfile.lock | 2 +- integration/helpers/app.go | 2 +- .../update_destination_command_test.go | 155 +++++++ 16 files changed, 975 insertions(+), 2 deletions(-) create mode 100644 command/v7/update_destination_command.go create mode 100644 command/v7/update_destination_command_test.go create mode 100644 integration/v7/isolated/update_destination_command_test.go diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index e7f87fab036..5bca12c73b0 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -162,6 +162,7 @@ type CloudControllerClient interface { UpdateApplicationRestart(appGUID string) (resources.Application, ccv3.Warnings, error) UpdateApplicationStart(appGUID string) (resources.Application, ccv3.Warnings, error) UpdateApplicationStop(appGUID string) (resources.Application, ccv3.Warnings, error) + UpdateDestination(routeGUID string, destinationGUID string, protocol string) (ccv3.Warnings, error) UpdateBuildpack(buildpack resources.Buildpack) (resources.Buildpack, ccv3.Warnings, error) UpdateEnvironmentVariableGroup(group constant.EnvironmentVariableGroupName, envVars resources.EnvironmentVariables) (resources.EnvironmentVariables, ccv3.Warnings, error) UpdateFeatureFlag(flag resources.FeatureFlag) (resources.FeatureFlag, ccv3.Warnings, error) diff --git a/actor/v7action/route.go b/actor/v7action/route.go index d59c318c61f..e91d5434473 100644 --- a/actor/v7action/route.go +++ b/actor/v7action/route.go @@ -398,6 +398,10 @@ func (actor Actor) MapRoute(routeGUID string, appGUID string, destinationProtoco return Warnings(warnings), err } +func (actor Actor) UpdateDestination(routeGUID string, destinationGUID string, protocol string) (Warnings, error) { + warnings, err := actor.CloudControllerClient.UpdateDestination(routeGUID, destinationGUID, protocol) + return Warnings(warnings), err +} func (actor Actor) UnmapRoute(routeGUID string, destinationGUID string) (Warnings, error) { warnings, err := actor.CloudControllerClient.UnmapRoute(routeGUID, destinationGUID) return Warnings(warnings), err diff --git a/actor/v7action/route_test.go b/actor/v7action/route_test.go index 064b1863761..56381a0032f 100644 --- a/actor/v7action/route_test.go +++ b/actor/v7action/route_test.go @@ -1631,6 +1631,49 @@ var _ = Describe("Route Actions", func() { }) }) + Describe("UpdateDestination", func() { + var ( + routeGUID string + destinationGUID string + protocol string + + executeErr error + warnings Warnings + ) + + JustBeforeEach(func() { + warnings, executeErr = actor.UpdateDestination(routeGUID, destinationGUID, protocol) + }) + + BeforeEach(func() { + routeGUID = "route-guid" + destinationGUID = "destination-guid" + protocol = "http2" + }) + + When("the cloud controller client errors", func() { + BeforeEach(func() { + fakeCloudControllerClient.UpdateDestinationReturns(ccv3.Warnings{"unmap-route-warning"}, errors.New("unmap-route-error")) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError(errors.New("unmap-route-error"))) + Expect(warnings).To(ConsistOf("unmap-route-warning")) + }) + }) + + When("the cloud controller client succeeds", func() { + BeforeEach(func() { + fakeCloudControllerClient.UpdateDestinationReturns(ccv3.Warnings{"unmap-route-warning"}, nil) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(warnings).To(ConsistOf("unmap-route-warning")) + }) + }) + }) + Describe("DeleteOrphanedRoutes", func() { var ( spaceGUID string diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 0c37e114ddf..bc955a70bba 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -2303,6 +2303,21 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + UpdateDestinationStub func(string, string, string) (ccv3.Warnings, error) + updateDestinationMutex sync.RWMutex + updateDestinationArgsForCall []struct { + arg1 string + arg2 string + arg3 string + } + updateDestinationReturns struct { + result1 ccv3.Warnings + result2 error + } + updateDestinationReturnsOnCall map[int]struct { + result1 ccv3.Warnings + result2 error + } UpdateEnvironmentVariableGroupStub func(constant.EnvironmentVariableGroupName, resources.EnvironmentVariables) (resources.EnvironmentVariables, ccv3.Warnings, error) updateEnvironmentVariableGroupMutex sync.RWMutex updateEnvironmentVariableGroupArgsForCall []struct { @@ -12694,6 +12709,71 @@ func (fake *FakeCloudControllerClient) UpdateBuildpackReturnsOnCall(i int, resul }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) UpdateDestination(arg1 string, arg2 string, arg3 string) (ccv3.Warnings, error) { + fake.updateDestinationMutex.Lock() + ret, specificReturn := fake.updateDestinationReturnsOnCall[len(fake.updateDestinationArgsForCall)] + fake.updateDestinationArgsForCall = append(fake.updateDestinationArgsForCall, struct { + arg1 string + arg2 string + arg3 string + }{arg1, arg2, arg3}) + fake.recordInvocation("UpdateDestination", []interface{}{arg1, arg2, arg3}) + fake.updateDestinationMutex.Unlock() + if fake.UpdateDestinationStub != nil { + return fake.UpdateDestinationStub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.updateDestinationReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeCloudControllerClient) UpdateDestinationCallCount() int { + fake.updateDestinationMutex.RLock() + defer fake.updateDestinationMutex.RUnlock() + return len(fake.updateDestinationArgsForCall) +} + +func (fake *FakeCloudControllerClient) UpdateDestinationCalls(stub func(string, string, string) (ccv3.Warnings, error)) { + fake.updateDestinationMutex.Lock() + defer fake.updateDestinationMutex.Unlock() + fake.UpdateDestinationStub = stub +} + +func (fake *FakeCloudControllerClient) UpdateDestinationArgsForCall(i int) (string, string, string) { + fake.updateDestinationMutex.RLock() + defer fake.updateDestinationMutex.RUnlock() + argsForCall := fake.updateDestinationArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeCloudControllerClient) UpdateDestinationReturns(result1 ccv3.Warnings, result2 error) { + fake.updateDestinationMutex.Lock() + defer fake.updateDestinationMutex.Unlock() + fake.UpdateDestinationStub = nil + fake.updateDestinationReturns = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeCloudControllerClient) UpdateDestinationReturnsOnCall(i int, result1 ccv3.Warnings, result2 error) { + fake.updateDestinationMutex.Lock() + defer fake.updateDestinationMutex.Unlock() + fake.UpdateDestinationStub = nil + if fake.updateDestinationReturnsOnCall == nil { + fake.updateDestinationReturnsOnCall = make(map[int]struct { + result1 ccv3.Warnings + result2 error + }) + } + fake.updateDestinationReturnsOnCall[i] = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeCloudControllerClient) UpdateEnvironmentVariableGroup(arg1 constant.EnvironmentVariableGroupName, arg2 resources.EnvironmentVariables) (resources.EnvironmentVariables, ccv3.Warnings, error) { fake.updateEnvironmentVariableGroupMutex.Lock() ret, specificReturn := fake.updateEnvironmentVariableGroupReturnsOnCall[len(fake.updateEnvironmentVariableGroupArgsForCall)] @@ -14611,6 +14691,8 @@ func (fake *FakeCloudControllerClient) Invocations() map[string][][]interface{} defer fake.updateApplicationStopMutex.RUnlock() fake.updateBuildpackMutex.RLock() defer fake.updateBuildpackMutex.RUnlock() + fake.updateDestinationMutex.RLock() + defer fake.updateDestinationMutex.RUnlock() fake.updateEnvironmentVariableGroupMutex.RLock() defer fake.updateEnvironmentVariableGroupMutex.RUnlock() fake.updateFeatureFlagMutex.RLock() diff --git a/api/cloudcontroller/ccv3/internal/api_routes.go b/api/cloudcontroller/ccv3/internal/api_routes.go index f1d637933f3..8f2239316c8 100644 --- a/api/cloudcontroller/ccv3/internal/api_routes.go +++ b/api/cloudcontroller/ccv3/internal/api_routes.go @@ -112,6 +112,7 @@ const ( PatchApplicationFeaturesRequest = "PatchApplicationFeatures" PatchEnvironmentVariableGroupRequest = "PatchEnvironmentVariableGroup" PatchBuildpackRequest = "PatchBuildpack" + PatchDestinationRequest = "PatchDestination" PatchDomainRequest = "PatchDomain" PatchFeatureFlagRequest = "PatchFeatureFlag" PatchOrganizationRelationshipDefaultIsolationSegmentRequest = "PatchOrganizationRelationshipDefaultIsolationSegment" @@ -275,6 +276,7 @@ var APIRoutes = map[string]Route{ GetRouteDestinationsRequest: {Path: "/v3/routes/:route_guid/destinations", Method: http.MethodGet}, MapRouteRequest: {Path: "/v3/routes/:route_guid/destinations", Method: http.MethodPost}, UnmapRouteRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodDelete}, + PatchDestinationRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodPatch}, GetSecurityGroupsRequest: {Path: "/v3/security_groups", Method: http.MethodGet}, PostSecurityGroupRequest: {Path: "/v3/security_groups", Method: http.MethodPost}, DeleteSecurityGroupRequest: {Path: "/v3/security_groups/:security_group_guid", Method: http.MethodDelete}, diff --git a/api/cloudcontroller/ccv3/route.go b/api/cloudcontroller/ccv3/route.go index 947461c1ee8..b40aed980c9 100644 --- a/api/cloudcontroller/ccv3/route.go +++ b/api/cloudcontroller/ccv3/route.go @@ -131,3 +131,20 @@ func (client Client) UnmapRoute(routeGUID string, destinationGUID string) (Warni return warnings, err } + +func (client Client) UpdateDestination(routeGUID string, destinationGUID string, protocol string) (Warnings, error) { + type body struct { + Protocol string `json:"protocol"` + } + requestBody := body{ + Protocol: protocol, + } + var responseBody resources.Build + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.PatchDestinationRequest, + URIParams: internal.Params{"route_guid": routeGUID, "destination_guid": destinationGUID}, + RequestBody: &requestBody, + ResponseBody: &responseBody, + }) + return warnings, err +} diff --git a/api/cloudcontroller/ccv3/route_test.go b/api/cloudcontroller/ccv3/route_test.go index 764b2787848..1ef7b6d2c5b 100644 --- a/api/cloudcontroller/ccv3/route_test.go +++ b/api/cloudcontroller/ccv3/route_test.go @@ -798,6 +798,93 @@ var _ = Describe("Route", func() { }) }) + Describe("UpdateDestination", func() { + var ( + routeGUID string + destinationGUID string + protocol string + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + warnings, executeErr = client.UpdateDestination(routeGUID, destinationGUID, protocol) + }) + + When("the request succeeds", func() { + routeGUID = "route-guid" + destinationGUID = "destination-guid" + protocol = "http2" + + expectedBody := fmt.Sprintf(`{ + "protocol": "%s" + }`, protocol) + + BeforeEach(func() { + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodPatch, "/v3/routes/route-guid/destinations/destination-guid"), + VerifyJSON(expectedBody), + RespondWith(http.StatusNoContent, nil, http.Header{ + "X-Cf-Warnings": {"this is a warning"}, + }), + ), + ) + }) + + It("returns all warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("this is a warning")) + }) + }) + + When("the cloud controller returns errors and warnings", func() { + BeforeEach(func() { + response := `{ + "errors": [ + { + "code": 10008, + "detail": "The request is semantically invalid: command presence", + "title": "CF-UnprocessableEntity" + }, + { + "code": 10010, + "detail": "Isolation segment not found", + "title": "CF-ResourceNotFound" + } + ] + }` + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodPatch, "/v3/routes/route-guid/destinations/destination-guid"), + RespondWith(http.StatusTeapot, response, http.Header{ + "X-Cf-Warnings": {"this is a warning"}, + }), + ), + ) + }) + + It("returns the error and all warnings", func() { + Expect(executeErr).To(MatchError(ccerror.MultiError{ + ResponseCode: http.StatusTeapot, + Errors: []ccerror.V3Error{ + { + Code: 10008, + Detail: "The request is semantically invalid: command presence", + Title: "CF-UnprocessableEntity", + }, + { + Code: 10010, + Detail: "Isolation segment not found", + Title: "CF-ResourceNotFound", + }, + }, + })) + Expect(warnings).To(ConsistOf("this is a warning")) + }) + }) + }) + Describe("DeleteOrphanedRoutes", func() { var ( spaceGUID string diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index 6cb264ed745..83300833d79 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -187,6 +187,7 @@ type commandList struct { UnsharePrivateDomain v7.UnsharePrivateDomainCommand `command:"unshare-private-domain" description:"Unshare a private domain with a specific org"` UnshareService v7.UnshareServiceCommand `command:"unshare-service" description:"Unshare a shared service instance from a space"` UpdateBuildpack v7.UpdateBuildpackCommand `command:"update-buildpack" description:"Update a buildpack"` + UpdateDestination v7.UpdateDestinationCommand `command:"update-destination" description:"Updates the destination protocol for a route"` UpdateOrgQuota v7.UpdateOrgQuotaCommand `command:"update-org-quota" alias:"update-quota" description:"Update an existing organization quota"` UpdateSecurityGroup v7.UpdateSecurityGroupCommand `command:"update-security-group" description:"Update a security group"` UpdateService v7.UpdateServiceCommand `command:"update-service" description:"Update a service instance"` diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 3dac8169748..e6d19c3d859 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -67,6 +67,7 @@ var HelpCategoryList = []HelpCategory{ {"routes", "route"}, {"create-route", "check-route", "map-route", "unmap-route", "delete-route"}, {"delete-orphaned-routes"}, + {"update-destination"}, }, }, { diff --git a/command/v7/actor.go b/command/v7/actor.go index d8bc2a17dca..b62365e4a90 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -232,6 +232,7 @@ type Actor interface { UpdateApplicationLabelsByApplicationName(string, string, map[string]types.NullString) (v7action.Warnings, error) UpdateBuildpackByNameAndStack(buildpackName string, buildpackStack string, buildpack resources.Buildpack) (resources.Buildpack, v7action.Warnings, error) UpdateBuildpackLabelsByBuildpackNameAndStack(string, string, map[string]types.NullString) (v7action.Warnings, error) + UpdateDestination(string, string, string) (v7action.Warnings, error) UpdateDomainLabelsByDomainName(string, map[string]types.NullString) (v7action.Warnings, error) UpdateManagedServiceInstance(params v7action.UpdateManagedServiceInstanceParams) (chan v7action.PollJobEvent, v7action.Warnings, error) UpgradeManagedServiceInstance(serviceInstanceName, spaceGUID string) (v7action.Warnings, error) diff --git a/command/v7/update_destination_command.go b/command/v7/update_destination_command.go new file mode 100644 index 00000000000..082b693db3f --- /dev/null +++ b/command/v7/update_destination_command.go @@ -0,0 +1,117 @@ +package v7 + +import ( + "errors" + + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/command/flag" +) + +type UpdateDestinationCommand struct { + BaseCommand + + RequiredArgs flag.AppDomain `positional-args:"yes"` + Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` + AppProtocol string `long:"app-protocol" description:"New Protocol for the route destination (http1 or http2). Only applied to HTTP routes"` + Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` + + relatedCommands interface{} `related_commands:"routes, map-route, create-route, unmap-route"` +} + +func (cmd UpdateDestinationCommand) Usage() string { + return ` +Edit an existing HTTP route: + CF_NAME update-destination APP_NAME DOMAIN [--hostname HOSTNAME] [--app-protocol PROTOCOL] [--path PATH]` +} + +func (cmd UpdateDestinationCommand) Examples() string { + return ` +CF_NAME update-destination my-app example.com --hostname myhost --app-protocol http2 # myhost.example.com +CF_NAME update destination my-app example.com --hostname myhost --path foo --app-protocol http2 # myhost.example.com/foo` +} + +func (cmd UpdateDestinationCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + domain, warnings, err := cmd.Actor.GetDomainByName(cmd.RequiredArgs.Domain) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + spaceGUID := cmd.Config.TargetedSpace().GUID + app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.RequiredArgs.App, spaceGUID) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + path := cmd.Path.Path + route, warnings, err := cmd.Actor.GetRouteByAttributes(domain, cmd.Hostname, path, 0) + cmd.UI.DisplayWarnings(warnings) + url := desiredURL(domain.Name, cmd.Hostname, path, 0) + if err != nil { + if _, ok := err.(actionerror.RouteNotFoundError); ok { + cmd.UI.DisplayText("Route to be updated does not exist.") + return err + } + return err + } + + dest, err := cmd.Actor.GetRouteDestinationByAppGUID(route, app.GUID) + if err != nil { + if _, ok := err.(actionerror.RouteDestinationNotFoundError); !ok { + cmd.UI.DisplayText("Route's destination to be updated does not exist.") + return err + } + } + + if cmd.AppProtocol == "" { + cmd.AppProtocol = "http1" + } + + if cmd.AppProtocol == "tcp" { + return errors.New("Destination protocol must be 'http1' or 'http2'") + } + + if dest.Protocol == cmd.AppProtocol { + cmd.UI.DisplayText(" App '{{ .AppName }}' is already using '{{ .AppProtocol }}'. Nothing has been updated", map[string]interface{}{ + "AppName": cmd.RequiredArgs.App, + "AppProtocol": cmd.AppProtocol, + }) + cmd.UI.DisplayOK() + return nil + } + + cmd.UI.DisplayTextWithFlavor("Updating destination protocol from {{.OldProtocol}} to {{.NewProtocol}} for route {{.URL}} in org {{.OrgName}} / space {{.SpaceName}} as {{.User}}...", + map[string]interface{}{ + "OldProtocol": dest.Protocol, + "NewProtocol": cmd.AppProtocol, + "URL": url, + "User": user.Name, + "SpaceName": cmd.Config.TargetedSpace().Name, + "OrgName": cmd.Config.TargetedOrganization().Name, + }) + + warnings, err = cmd.Actor.UpdateDestination( + route.GUID, + dest.GUID, + cmd.AppProtocol, + ) + + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + cmd.UI.DisplayOK() + + return nil +} diff --git a/command/v7/update_destination_command_test.go b/command/v7/update_destination_command_test.go new file mode 100644 index 00000000000..802a125b7ce --- /dev/null +++ b/command/v7/update_destination_command_test.go @@ -0,0 +1,380 @@ +package v7_test + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/cf/errors" + "code.cloudfoundry.org/cli/command/commandfakes" + "code.cloudfoundry.org/cli/command/flag" + v7 "code.cloudfoundry.org/cli/command/v7" + "code.cloudfoundry.org/cli/command/v7/v7fakes" + "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/util/configv3" + "code.cloudfoundry.org/cli/util/ui" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("update Command", func() { + var ( + cmd v7.UpdateDestinationCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + binaryName string + executeErr error + domainName string + appName string + appProtocol string + orgGUID string + spaceGUID string + hostname string + path string + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + binaryName = "faceman" + fakeConfig.BinaryNameReturns(binaryName) + + domainName = "some-domain.com" + appName = "super-app" + appProtocol = "http2" + orgGUID = "some-org-guid" + spaceGUID = "some-space-guid" + hostname = "hostname" + path = "path" + + cmd = v7.UpdateDestinationCommand{ + BaseCommand: v7.BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + RequiredArgs: flag.AppDomain{App: appName, Domain: domainName}, + AppProtocol: appProtocol, + Hostname: hostname, + Path: flag.V7RoutePath{Path: path}, + } + + fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space", GUID: spaceGUID}) + fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org", GUID: orgGUID}) + fakeConfig.CurrentUserReturns(configv3.User{Name: "some-user"}, nil) + + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + It("checks the target", func() { + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + + When("checking target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) + }) + It("returns an error", func() { + Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) + + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("the user is not logged in", func() { + var expectedErr error + + BeforeEach(func() { + expectedErr = errors.New("some current user error") + fakeActor.GetCurrentUserReturns(configv3.User{}, expectedErr) + }) + + It("return an error", func() { + Expect(executeErr).To(Equal(expectedErr)) + }) + }) + + When("the user is logged in and targeted", func() { + When("getting the domain errors", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns(resources.Domain{}, v7action.Warnings{"get-domain-warnings"}, errors.New("get-domain-error")) + }) + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(executeErr).To(MatchError(errors.New("get-domain-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(0)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(0)) + + Expect(fakeActor.GetRouteDestinationByAppGUIDCallCount()).To(Equal(0)) + + Expect(fakeActor.UpdateDestinationCallCount()).To(Equal(0)) + }) + }) + + When("getting the domain succeeds", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns( + resources.Domain{Name: domainName, GUID: "domain-guid"}, + v7action.Warnings{"get-domain-warnings"}, + nil, + ) + }) + + When("getting the app errors", func() { + BeforeEach(func() { + fakeActor.GetApplicationByNameAndSpaceReturns( + resources.Application{}, + v7action.Warnings{"get-app-warnings"}, + errors.New("get-app-error"), + ) + }) + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-app-warnings")) + Expect(executeErr).To(MatchError(errors.New("get-app-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + actualAppName, actualSpaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(actualAppName).To(Equal(appName)) + Expect(actualSpaceGUID).To(Equal(spaceGUID)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(0)) + + Expect(fakeActor.CreateRouteCallCount()).To(Equal(0)) + + Expect(fakeActor.MapRouteCallCount()).To(Equal(0)) + }) + }) + + When("getting the app succeeds", func() { + BeforeEach(func() { + fakeActor.GetApplicationByNameAndSpaceReturns( + resources.Application{GUID: "app-guid", Name: appName}, + v7action.Warnings{"get-app-warnings"}, + nil, + ) + }) + + When("getting the route errors", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{}, + v7action.Warnings{"get-route-warnings"}, + errors.New("get-route-error"), + ) + }) + + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-app-warnings")) + Expect(testUI.Err).To(Say("get-route-warnings")) + Expect(executeErr).To(MatchError(errors.New("get-route-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + actualAppName, actualSpaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(actualAppName).To(Equal(appName)) + Expect(actualSpaceGUID).To(Equal(spaceGUID)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.UpdateDestinationCallCount()).To(Equal(0)) + + }) + }) + + When("the requested route does not exist", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.RouteNotFoundError{}, + ) + }) + + It("displays error message", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-route-warnings")) + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + actualAppName, actualSpaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(actualAppName).To(Equal(appName)) + Expect(actualSpaceGUID).To(Equal(spaceGUID)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + }) + }) + + When("the requested route exists", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{GUID: "route-guid"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + When("getting the destination errors", func() { + BeforeEach(func() { + fakeActor.GetRouteDestinationByAppGUIDReturns( + resources.RouteDestination{}, + errors.New("get-destination-error"), + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError(errors.New("get-destination-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + actualAppName, actualSpaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(actualAppName).To(Equal(appName)) + Expect(actualSpaceGUID).To(Equal(spaceGUID)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetRouteDestinationByAppGUIDCallCount()).To(Equal(1)) + actualRoute, actualAppGUID := fakeActor.GetRouteDestinationByAppGUIDArgsForCall(0) + Expect(actualRoute.GUID).To(Equal("route-guid")) + Expect(actualAppGUID).To(Equal("app-guid")) + + Expect(fakeActor.UpdateDestinationCallCount()).To(Equal(0)) + }) + }) + When("the destination already exists", func() { + BeforeEach(func() { + fakeActor.GetRouteDestinationByAppGUIDReturns( + resources.RouteDestination{ + GUID: "route-dst-guid", + App: resources.RouteDestinationApp{ + GUID: "existing-app-guid", + }, + }, + nil, + ) + }) + + It("exits 0 with a helpful message that the destination protocol was changed", func() { + Expect(executeErr).ShouldNot(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + actualAppName, actualSpaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(actualAppName).To(Equal(appName)) + Expect(actualSpaceGUID).To(Equal(spaceGUID)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetRouteDestinationByAppGUIDCallCount()).To(Equal(1)) + actualRoute, actualAppGUID := fakeActor.GetRouteDestinationByAppGUIDArgsForCall(0) + Expect(actualRoute.GUID).To(Equal("route-guid")) + Expect(actualAppGUID).To(Equal("app-guid")) + Expect(fakeActor.UpdateDestinationCallCount()).To(Equal(1)) + }) + }) + + When("the destination is not found", func() { + When("updating the destination errors", func() { + BeforeEach(func() { + fakeActor.UpdateDestinationReturns(v7action.Warnings{"update-dest-warnings"}, errors.New("map-route-error")) + fakeActor.GetRouteDestinationByAppGUIDReturns( + resources.RouteDestination{ + GUID: "route-dst-guid", + App: resources.RouteDestinationApp{ + GUID: "existing-app-guid", + }, + }, + nil, + ) + }) + + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-app-warnings")) + Expect(testUI.Err).To(Say("get-route-warnings")) + Expect(testUI.Err).To(Say("update-dest-warnings")) + Expect(executeErr).To(MatchError(errors.New("map-route-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + actualAppName, actualSpaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(actualAppName).To(Equal(appName)) + Expect(actualSpaceGUID).To(Equal(spaceGUID)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.UpdateDestinationCallCount()).To(Equal(1)) + actualRouteGUID, destinationGUID, protocol := fakeActor.UpdateDestinationArgsForCall(0) + Expect(actualRouteGUID).To(Equal("route-guid")) + Expect(destinationGUID).To(Equal("route-dst-guid")) + Expect(protocol).To(Equal("http2")) + }) + }) + }) + }) + }) + }) + }) +}) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 8065bc84341..523de5ea272 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -3213,6 +3213,21 @@ type FakeActor struct { result1 v7action.Warnings result2 error } + UpdateDestinationStub func(string, string, string) (v7action.Warnings, error) + updateDestinationMutex sync.RWMutex + updateDestinationArgsForCall []struct { + arg1 string + arg2 string + arg3 string + } + updateDestinationReturns struct { + result1 v7action.Warnings + result2 error + } + updateDestinationReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } UpdateDomainLabelsByDomainNameStub func(string, map[string]types.NullString) (v7action.Warnings, error) updateDomainLabelsByDomainNameMutex sync.RWMutex updateDomainLabelsByDomainNameArgsForCall []struct { @@ -17339,6 +17354,71 @@ func (fake *FakeActor) UpdateBuildpackLabelsByBuildpackNameAndStackReturnsOnCall }{result1, result2} } +func (fake *FakeActor) UpdateDestination(arg1 string, arg2 string, arg3 string) (v7action.Warnings, error) { + fake.updateDestinationMutex.Lock() + ret, specificReturn := fake.updateDestinationReturnsOnCall[len(fake.updateDestinationArgsForCall)] + fake.updateDestinationArgsForCall = append(fake.updateDestinationArgsForCall, struct { + arg1 string + arg2 string + arg3 string + }{arg1, arg2, arg3}) + fake.recordInvocation("UpdateDestination", []interface{}{arg1, arg2, arg3}) + fake.updateDestinationMutex.Unlock() + if fake.UpdateDestinationStub != nil { + return fake.UpdateDestinationStub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.updateDestinationReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) UpdateDestinationCallCount() int { + fake.updateDestinationMutex.RLock() + defer fake.updateDestinationMutex.RUnlock() + return len(fake.updateDestinationArgsForCall) +} + +func (fake *FakeActor) UpdateDestinationCalls(stub func(string, string, string) (v7action.Warnings, error)) { + fake.updateDestinationMutex.Lock() + defer fake.updateDestinationMutex.Unlock() + fake.UpdateDestinationStub = stub +} + +func (fake *FakeActor) UpdateDestinationArgsForCall(i int) (string, string, string) { + fake.updateDestinationMutex.RLock() + defer fake.updateDestinationMutex.RUnlock() + argsForCall := fake.updateDestinationArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeActor) UpdateDestinationReturns(result1 v7action.Warnings, result2 error) { + fake.updateDestinationMutex.Lock() + defer fake.updateDestinationMutex.Unlock() + fake.UpdateDestinationStub = nil + fake.updateDestinationReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) UpdateDestinationReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.updateDestinationMutex.Lock() + defer fake.updateDestinationMutex.Unlock() + fake.UpdateDestinationStub = nil + if fake.updateDestinationReturnsOnCall == nil { + fake.updateDestinationReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.updateDestinationReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) UpdateDomainLabelsByDomainName(arg1 string, arg2 map[string]types.NullString) (v7action.Warnings, error) { fake.updateDomainLabelsByDomainNameMutex.Lock() ret, specificReturn := fake.updateDomainLabelsByDomainNameReturnsOnCall[len(fake.updateDomainLabelsByDomainNameArgsForCall)] @@ -19266,6 +19346,8 @@ func (fake *FakeActor) Invocations() map[string][][]interface{} { defer fake.updateBuildpackByNameAndStackMutex.RUnlock() fake.updateBuildpackLabelsByBuildpackNameAndStackMutex.RLock() defer fake.updateBuildpackLabelsByBuildpackNameAndStackMutex.RUnlock() + fake.updateDestinationMutex.RLock() + defer fake.updateDestinationMutex.RUnlock() fake.updateDomainLabelsByDomainNameMutex.RLock() defer fake.updateDomainLabelsByDomainNameMutex.RUnlock() fake.updateManagedServiceInstanceMutex.RLock() diff --git a/integration/assets/go_calls_ruby/Gemfile.lock b/integration/assets/go_calls_ruby/Gemfile.lock index f1d8dee14cd..51e876ea324 100644 --- a/integration/assets/go_calls_ruby/Gemfile.lock +++ b/integration/assets/go_calls_ruby/Gemfile.lock @@ -8,4 +8,4 @@ PLATFORMS DEPENDENCIES BUNDLED WITH - 1.14.6 + 2.2.27 diff --git a/integration/helpers/app.go b/integration/helpers/app.go index df93f60ac7e..92cccfa3902 100644 --- a/integration/helpers/app.go +++ b/integration/helpers/app.go @@ -194,7 +194,7 @@ PLATFORMS DEPENDENCIES BUNDLED WITH - 1.15.0 + 2.1.4 `), 0666) Expect(err).ToNot(HaveOccurred()) diff --git a/integration/v7/isolated/update_destination_command_test.go b/integration/v7/isolated/update_destination_command_test.go new file mode 100644 index 00000000000..6052d8795b3 --- /dev/null +++ b/integration/v7/isolated/update_destination_command_test.go @@ -0,0 +1,155 @@ +package isolated + +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" + . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("update destination command", func() { + Context("Heltp", func() { + It("appears in cf help -a", func() { + session := helpers.CF("help", "-a") + + Eventually(session).Should(Exit(0)) + Expect(session).To(HaveCommandInCategoryWithDescription("update-destination", "ROUTES", "Updates the destination protocol for a route")) + }) + + It("displays the help information", func() { + session := helpers.CF("update-destination", "--help") + Eventually(session).Should(Say(`NAME:`)) + Eventually(session).Should(Say(`update-destination - Updates the destination protocol for a route`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`USAGE:`)) + Eventually(session).Should(Say(`Edit an existing HTTP route`)) + Eventually(session).Should(Say(`cf update-destination APP_NAME DOMAIN \[--hostname HOSTNAME\] \[--app-protocol PROTOCOL\] \[--path PATH\]\n`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`EXAMPLES:`)) + Eventually(session).Should(Say(`cf update-destination my-app example.com --hostname myhost --app-protocol http2 # myhost.example.com`)) + Eventually(session).Should(Say(`cf update destination my-app example.com --hostname myhost --path foo --app-protocol http2 # myhost.example.com/foo`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`OPTIONS:`)) + Eventually(session).Should(Say(`--hostname, -n\s+Hostname for the HTTP route \(required for shared domains\)`)) + Eventually(session).Should(Say(`--app-protocol\s+New Protocol for the route destination \(http1 or http2\). Only applied to HTTP routes`)) + Eventually(session).Should(Say(`--path\s+Path for the HTTP route`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`SEE ALSO:`)) + Eventually(session).Should(Say(`create-route, map-route, routes, unmap-route`)) + + Eventually(session).Should(Exit(0)) + }) + }) + + When("the environment is not setup correctly", func() { + It("fails with the appropriate errors", func() { + helpers.CheckEnvironmentTargetedCorrectly(true, false, ReadOnlyOrg, "update-destination", "app-name", "some-domain") + }) + }) + + When("the environment is set up correctly", func() { + var ( + userName string + orgName string + spaceName string + ) + + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionHTTP2RoutingV3) + orgName = helpers.NewOrgName() + spaceName = helpers.NewSpaceName() + + helpers.SetupCF(orgName, spaceName) + userName, _ = helpers.GetCredentials() + }) + + AfterEach(func() { + helpers.QuickDeleteOrg(orgName) + }) + + When("the domain exists", func() { + var ( + domainName string + ) + + BeforeEach(func() { + domainName = helpers.NewDomainName() + }) + + When("the route exists", func() { + var ( + domain helpers.Domain + appName string + hostname string + ) + + When("it's an HTTP/1 route", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "key-lime-pie" + appName = "killer" + domain.CreatePrivate() + Eventually(helpers.CF("create-app", appName)).Should(Exit(0)) + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + Eventually(helpers.CF("map-route", appName, domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("updates the destination protocol to http2 ", func() { + session := helpers.CF("update-destination", appName, domainName, "--hostname", hostname, "--app-protocol", "http2") + Eventually(session).Should(Say(`Updating destination protocol from %s to %s for route %s.%s in org %s / space %s as %s...`, "http1", "http2", hostname, domainName, orgName, spaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("it's an HTTP/2 route", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "key-lime-pie" + appName = "killer2" + domain.CreatePrivate() + Eventually(helpers.CF("create-app", appName)).Should(Exit(0)) + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + Eventually(helpers.CF("map-route", appName, domain.Name, "--hostname", hostname, "--app-protocol", "http2")).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("updates the destination protocol to http1 ", func() { + session := helpers.CF("update-destination", appName, domainName, "--hostname", hostname, "--app-protocol", "http1") + Eventually(session).Should(Say(`Updating destination protocol from %s to %s for route %s.%s in org %s / space %s as %s...`, "http2", "http1", hostname, domainName, orgName, spaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + + It("does nothing when the app-protocol is the same", func() { + session := helpers.CF("update-destination", appName, domainName, "--hostname", hostname, "--app-protocol", "http2") + Eventually(session).Should(Say(`App '%s' is already using '%s'\. Nothing has been updated`, appName, "http2")) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + + It("sets the destination protocol to 'http1' when the app-protocol is not provided", func() { + session := helpers.CF("update-destination", appName, domainName, "--hostname", hostname) + Eventually(session).Should(Say(`Updating destination protocol from %s to %s for route %s.%s in org %s / space %s as %s...`, "http2", "http1", hostname, domainName, orgName, spaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + }) + }) + }) +}) From 7de39e53a46445e6af2197e4279a22f9095795d2 Mon Sep 17 00:00:00 2001 From: Hector J Calderon Date: Wed, 9 Feb 2022 15:36:49 -0500 Subject: [PATCH 006/543] [Bugfix] Idempotency when trying to create a service twice It was implemented everything for this feature but the error handler when CAPI V3 answered with a 422 status code with the message the service instance name is taken. --- api/cloudcontroller/ccv3/errors.go | 3 +++ api/cloudcontroller/ccv3/errors_test.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/api/cloudcontroller/ccv3/errors.go b/api/cloudcontroller/ccv3/errors.go index 1a593a5fe1f..c89a60d126a 100644 --- a/api/cloudcontroller/ccv3/errors.go +++ b/api/cloudcontroller/ccv3/errors.go @@ -171,6 +171,9 @@ func handleUnprocessableEntity(errorResponse ccerror.V3Error) error { case strings.Contains(errorString, "Assign a droplet before starting this app."): return ccerror.InvalidStartError{} + case strings.Contains(errorString, + "The service instance name is taken"): + return ccerror.ServiceInstanceNameTakenError{Message: err.Message} case orgNameTakenRegexp.MatchString(errorString): return ccerror.OrganizationNameTakenError{UnprocessableEntityError: err} case roleExistsRegexp.MatchString(errorString): diff --git a/api/cloudcontroller/ccv3/errors_test.go b/api/cloudcontroller/ccv3/errors_test.go index 8785a9a848c..6873746014c 100644 --- a/api/cloudcontroller/ccv3/errors_test.go +++ b/api/cloudcontroller/ccv3/errors_test.go @@ -486,6 +486,27 @@ var _ = Describe("Error Wrapper", func() { }) }) + When("the service instance name is taken", func() { + BeforeEach(func() { + serverResponse = ` +{ + "errors": [ + { + "code": 10008, + "detail": "The service instance name is taken", + "title": "CF-UnprocessableEntity" + } + ] +}` + }) + + It("returns an ServiceInstanceNameTakenError", func() { + Expect(makeError).To(MatchError(ccerror.ServiceInstanceNameTakenError{ + Message: "The service instance name is taken", + })) + }) + }) + When("the buildpack is invalid", func() { BeforeEach(func() { serverResponse = ` From 7240c016d04339e568860b036db4edff48d67d36 Mon Sep 17 00:00:00 2001 From: Matt Royal Date: Fri, 18 Mar 2022 10:56:56 -0700 Subject: [PATCH 007/543] Fix selfcontained tests failing due to /whoami (#2266) Authored-by: Matt Royal --- .../v7/selfcontained/kubernetes_auth_test.go | 20 ++++-- .../v7/selfcontained/login_command_test.go | 17 +++-- .../v7/selfcontained/logout_command_test.go | 71 +++++++++++++++++-- 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/integration/v7/selfcontained/kubernetes_auth_test.go b/integration/v7/selfcontained/kubernetes_auth_test.go index 113c8e4f175..dfaa8d8bfa0 100644 --- a/integration/v7/selfcontained/kubernetes_auth_test.go +++ b/integration/v7/selfcontained/kubernetes_auth_test.go @@ -4,14 +4,15 @@ import ( "net/http" "path/filepath" - "code.cloudfoundry.org/cli/integration/helpers" - "code.cloudfoundry.org/cli/integration/v7/selfcontained/fake" - "code.cloudfoundry.org/cli/resources" - "code.cloudfoundry.org/cli/util/configv3" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" apiv1 "k8s.io/client-go/tools/clientcmd/api/v1" + + "code.cloudfoundry.org/cli/integration/helpers" + "code.cloudfoundry.org/cli/integration/v7/selfcontained/fake" + "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/util/configv3" ) var _ = Describe("auth-provider", func() { @@ -29,13 +30,19 @@ var _ = Describe("auth-provider", func() { "resources": []resources.Application{}, }, }, + "GET /whoami": { + Code: http.StatusOK, Body: map[string]interface{}{ + "name": "my-user", + "kind": "User", + }, + }, }, } apiServer.SetConfiguration(apiConfig) helpers.SetConfig(func(config *configv3.Config) { config.ConfigFile.Target = apiServer.URL() config.ConfigFile.CFOnK8s.Enabled = true - config.ConfigFile.CFOnK8s.AuthInfo = "one" + config.ConfigFile.CFOnK8s.AuthInfo = "my-user" config.ConfigFile.TargetedOrganization = configv3.Organization{ GUID: "my-org", Name: "My Org", @@ -52,7 +59,8 @@ var _ = Describe("auth-provider", func() { APIVersion: "v1", AuthInfos: []apiv1.NamedAuthInfo{ { - Name: "one", AuthInfo: apiv1.AuthInfo{ + Name: "my-user", + AuthInfo: apiv1.AuthInfo{ AuthProvider: &apiv1.AuthProviderConfig{ Name: "oidc", Config: map[string]string{ diff --git a/integration/v7/selfcontained/login_command_test.go b/integration/v7/selfcontained/login_command_test.go index 5e41d783fec..5def6b7b457 100644 --- a/integration/v7/selfcontained/login_command_test.go +++ b/integration/v7/selfcontained/login_command_test.go @@ -6,16 +6,17 @@ import ( "os" "path/filepath" - "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" - "code.cloudfoundry.org/cli/integration/helpers" - "code.cloudfoundry.org/cli/integration/v7/selfcontained/fake" - "code.cloudfoundry.org/cli/resources" - "code.cloudfoundry.org/cli/util/configv3" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" apiv1 "k8s.io/client-go/tools/clientcmd/api/v1" + + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" + "code.cloudfoundry.org/cli/integration/helpers" + "code.cloudfoundry.org/cli/integration/v7/selfcontained/fake" + "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/util/configv3" ) var _ = Describe("LoginCommand", func() { @@ -45,6 +46,12 @@ var _ = Describe("LoginCommand", func() { "resources": []resources.Organization{}, }, }, + "GET /whoami": { + Code: http.StatusOK, Body: map[string]interface{}{ + "name": "two", + "kind": "User", + }, + }, }, } apiServer.SetConfiguration(apiConfig) diff --git a/integration/v7/selfcontained/logout_command_test.go b/integration/v7/selfcontained/logout_command_test.go index 55e53f35a5d..b607be4146a 100644 --- a/integration/v7/selfcontained/logout_command_test.go +++ b/integration/v7/selfcontained/logout_command_test.go @@ -1,23 +1,86 @@ package selfcontained_test import ( - "code.cloudfoundry.org/cli/integration/helpers" - "code.cloudfoundry.org/cli/util/configv3" + "net/http" + "path/filepath" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" + apiv1 "k8s.io/client-go/tools/clientcmd/api/v1" + + "code.cloudfoundry.org/cli/integration/helpers" + "code.cloudfoundry.org/cli/integration/v7/selfcontained/fake" + "code.cloudfoundry.org/cli/util/configv3" ) var _ = Describe("cf logout", func() { BeforeEach(func() { helpers.SetConfig(func(config *configv3.Config) { config.ConfigFile.CFOnK8s.Enabled = true - config.ConfigFile.CFOnK8s.AuthInfo = "something" + config.ConfigFile.CFOnK8s.AuthInfo = "my-user" }) + + apiServer.SetConfiguration(fake.CFAPIConfig{Routes: map[string]fake.Response{ + "GET /whoami": { + Code: http.StatusOK, Body: map[string]interface{}{ + "name": "my-user", + "kind": "User", + }, + }, + }}) + + kubeConfig := apiv1.Config{ + Kind: "Config", + APIVersion: "v1", + AuthInfos: []apiv1.NamedAuthInfo{ + { + Name: "my-user", + AuthInfo: apiv1.AuthInfo{ + AuthProvider: &apiv1.AuthProviderConfig{ + Name: "oidc", + Config: map[string]string{ + "id-token": string(token), + "idp-issuer-url": "-", + "client-id": "-", + }, + }, + }, + }, + }, + Clusters: []apiv1.NamedCluster{ + { + Name: "my-cluster", + Cluster: apiv1.Cluster{ + Server: "https://example.org", + }, + }, + }, + Contexts: []apiv1.NamedContext{ + { + Name: "my-context", + Context: apiv1.Context{ + Cluster: "my-cluster", + AuthInfo: "my-auth-info", + Namespace: "my-namespace", + }, + }, + }, + CurrentContext: "my-context", + } + + kubeConfigPath := filepath.Join(homeDir, ".kube", "config") + storeKubeConfig(kubeConfig, kubeConfigPath) + + env = helpers.CFEnv{ + EnvVars: map[string]string{ + "KUBECONFIG": kubeConfigPath, + }, + } }) JustBeforeEach(func() { - Eventually(helpers.CF("logout")).Should(gexec.Exit(0)) + Eventually(helpers.CustomCF(env, "logout")).Should(gexec.Exit(0)) }) It("clears the auth-info", func() { From f0dd1d7c012638da2cbe389ff57dd13240bd5351 Mon Sep 17 00:00:00 2001 From: Ryker Reed Date: Fri, 25 Mar 2022 12:18:01 -0400 Subject: [PATCH 008/543] Merge pull request #2267 from eirini-forks/add-auth-to-logs Add auth header to logcache API requests for cf-on-k8s --- .../wrapper/kubernetes_authentication.go | 98 +--- .../wrapper/kubernetes_authentication_test.go | 2 +- {command => api/logcache}/log_cache_client.go | 56 ++- api/shared/shared_suite_test.go | 25 + api/shared/sharedfakes/fake_round_tripper.go | 114 +++++ api/shared/wrap_for_cf_on_k8s.go | 124 +++++ api/shared/wrap_for_cf_on_k8s_test.go | 462 ++++++++++++++++++ cf/api/repository_locator.go | 9 +- command/v7/copy_source_command.go | 8 +- command/v7/logs_command.go | 6 +- command/v7/push_command.go | 13 +- command/v7/restage_command.go | 8 +- command/v7/restart_command.go | 9 +- command/v7/stage_package_command.go | 7 +- command/v7/start_command.go | 8 +- .../selfcontained/kubernetes_auth_log_test.go | 138 ++++++ 16 files changed, 967 insertions(+), 120 deletions(-) rename {command => api/logcache}/log_cache_client.go (76%) create mode 100644 api/shared/shared_suite_test.go create mode 100644 api/shared/sharedfakes/fake_round_tripper.go create mode 100644 api/shared/wrap_for_cf_on_k8s.go create mode 100644 api/shared/wrap_for_cf_on_k8s_test.go create mode 100644 integration/v7/selfcontained/kubernetes_auth_log_test.go diff --git a/api/cloudcontroller/wrapper/kubernetes_authentication.go b/api/cloudcontroller/wrapper/kubernetes_authentication.go index b683278077e..622bd580ccf 100644 --- a/api/cloudcontroller/wrapper/kubernetes_authentication.go +++ b/api/cloudcontroller/wrapper/kubernetes_authentication.go @@ -1,25 +1,12 @@ package wrapper import ( - "bytes" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "errors" - "fmt" "net/http" "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller" + "code.cloudfoundry.org/cli/api/shared" "code.cloudfoundry.org/cli/command" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/client-go/transport" - - _ "k8s.io/client-go/plugin/pkg/client/auth/azure" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" ) type KubernetesAuthentication struct { @@ -40,87 +27,16 @@ func NewKubernetesAuthentication( } func (a *KubernetesAuthentication) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error { - username, err := a.config.CurrentUserName() + roundTripper, err := shared.WrapForCFOnK8sAuth(a.config, a.k8sConfigGetter, connectionRoundTripper{ + connection: a.connection, + ccRequest: request, + ccResponse: passedResponse, + }) if err != nil { return err } - if username == "" { - return errors.New("current user not set") - } - - k8sConfig, err := a.k8sConfigGetter.Get() - if err != nil { - return err - } - - restConfig, err := clientcmd.NewDefaultClientConfig( - *k8sConfig, - &clientcmd.ConfigOverrides{ - Context: api.Context{AuthInfo: username}, - }).ClientConfig() - if err != nil { - return err - } - - tlsConfig, err := rest.TLSConfigFor(restConfig) - if err != nil { - return fmt.Errorf("failed to get tls config: %w", err) - } - - if tlsConfig != nil && tlsConfig.GetClientCertificate != nil { - cert, err := tlsConfig.GetClientCertificate(nil) - if err != nil { - return fmt.Errorf("failed to get client certificate: %w", err) - } - - if len(cert.Certificate) > 0 && cert.PrivateKey != nil { - var buf bytes.Buffer - - if err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}); err != nil { - return fmt.Errorf("could not convert certificate to PEM format: %w", err) - } - - key, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey) - if err != nil { - return fmt.Errorf("could not marshal private key: %w", err) - } - - if err := pem.Encode(&buf, &pem.Block{Type: "PRIVATE KEY", Bytes: key}); err != nil { - return fmt.Errorf("could not convert key to PEM format: %w", err) - } - - auth := "ClientCert " + base64.StdEncoding.EncodeToString(buf.Bytes()) - request.Header.Set("Authorization", auth) - - return a.connection.Make(request, passedResponse) - } - } - - transportConfig, err := restConfig.TransportConfig() - if err != nil { - return fmt.Errorf("failed to get transport config: %w", err) - } - - var roundtripper http.RoundTripper - if transportConfig.WrapTransport == nil { - // i.e. not auth-provider or exec plugin - roundtripper, err = transport.HTTPWrappersForConfig(transportConfig, connectionRoundTripper{ - connection: a.connection, - ccRequest: request, - ccResponse: passedResponse, - }) - if err != nil { - return fmt.Errorf("failed to create new transport: %w", err) - } - } else { - roundtripper = transportConfig.WrapTransport(connectionRoundTripper{ - connection: a.connection, - ccRequest: request, - ccResponse: passedResponse, - }) - } - _, err = roundtripper.RoundTrip(request.Request) + _, err = roundTripper.RoundTrip(request.Request) return err } diff --git a/api/cloudcontroller/wrapper/kubernetes_authentication_test.go b/api/cloudcontroller/wrapper/kubernetes_authentication_test.go index e46a282438e..b6c9fc3fb3f 100644 --- a/api/cloudcontroller/wrapper/kubernetes_authentication_test.go +++ b/api/cloudcontroller/wrapper/kubernetes_authentication_test.go @@ -119,7 +119,7 @@ var _ = Describe("KubernetesAuthentication", func() { }) }) - When("the chosen kubeernetes auth info is not present in kubeconfig", func() { + When("the chosen kubernetes auth info is not present in kubeconfig", func() { BeforeEach(func() { config.CurrentUserNameReturns("not-present", nil) }) diff --git a/command/log_cache_client.go b/api/logcache/log_cache_client.go similarity index 76% rename from command/log_cache_client.go rename to api/logcache/log_cache_client.go index f6884818225..c8bdc935954 100644 --- a/command/log_cache_client.go +++ b/api/logcache/log_cache_client.go @@ -1,4 +1,4 @@ -package command +package logcache import ( "fmt" @@ -8,8 +8,12 @@ import ( "strings" "time" - "code.cloudfoundry.org/cli/util" logcache "code.cloudfoundry.org/go-log-cache" + + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/api/shared" + "code.cloudfoundry.org/cli/command" + "code.cloudfoundry.org/cli/util" ) type RequestLoggerOutput interface { @@ -62,15 +66,23 @@ func (p *DebugPrinter) addOutput(output RequestLoggerOutput) { p.outputs = append(p.outputs, output) } +type userAgentHTTPClient struct { + c logcache.HTTPClient + userAgent string +} + +func (c *userAgentHTTPClient) Do(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", c.userAgent) + return c.c.Do(req) +} + type tokenHTTPClient struct { c logcache.HTTPClient accessToken func() string - userAgent string } func (c *tokenHTTPClient) Do(req *http.Request) (*http.Response, error) { req.Header.Set("Authorization", c.accessToken()) - req.Header.Set("User-Agent", c.userAgent) return c.c.Do(req) } @@ -93,9 +105,9 @@ func (c *httpDebugClient) Do(req *http.Request) (*http.Response, error) { return resp, err } -// NewLogCacheClient returns back a configured Log Cache Client. -func NewLogCacheClient(logCacheEndpoint string, config Config, ui UI) *logcache.Client { - tr := &http.Transport{ +// NewClient returns back a configured Log Cache Client. +func NewClient(logCacheEndpoint string, config command.Config, ui command.UI, k8sConfigGetter v7action.KubernetesConfigGetter) (*logcache.Client, error) { + var tr http.RoundTripper = &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: util.NewTLSConfig(nil, config.SkipSSLValidation()), DialContext: (&net.Dialer{ @@ -104,8 +116,19 @@ func NewLogCacheClient(logCacheEndpoint string, config Config, ui UI) *logcache. }).DialContext, } + if config.IsCFOnK8s() { + var err error + tr, err = shared.WrapForCFOnK8sAuth(config, k8sConfigGetter, tr) + if err != nil { + return nil, err + } + } + var client logcache.HTTPClient //nolint - client = &http.Client{Transport: tr} + client = &userAgentHTTPClient{ + c: &http.Client{Transport: tr}, + userAgent: fmt.Sprintf("%s/%s (%s; %s %s)", config.BinaryName(), config.BinaryVersion(), runtime.Version(), runtime.GOARCH, runtime.GOOS), + } verbose, location := config.Verbose() if verbose && ui != nil { @@ -118,16 +141,19 @@ func NewLogCacheClient(logCacheEndpoint string, config Config, ui UI) *logcache. client = &httpDebugClient{printer: printer, c: client} } - userAgent := fmt.Sprintf("%s/%s (%s; %s %s)", config.BinaryName(), config.BinaryVersion(), runtime.Version(), runtime.GOARCH, runtime.GOOS) - return logcache.NewClient( - logCacheEndpoint, - logcache.WithHTTPClient(&tokenHTTPClient{ + if !config.IsCFOnK8s() { + client = &tokenHTTPClient{ c: client, accessToken: config.AccessToken, - userAgent: userAgent, - }), - ) + } + } + + return logcache.NewClient( + logCacheEndpoint, + logcache.WithHTTPClient(client), + ), nil } + func headersString(header http.Header) string { var result string for name, values := range header { diff --git a/api/shared/shared_suite_test.go b/api/shared/shared_suite_test.go new file mode 100644 index 00000000000..bc69c85df0f --- /dev/null +++ b/api/shared/shared_suite_test.go @@ -0,0 +1,25 @@ +package shared_test + +import ( + "crypto/rand" + "crypto/rsa" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestShared(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Shared Wrapper Suite") +} + +var ( + keyPair *rsa.PrivateKey +) + +var _ = BeforeEach(func() { + var err error + keyPair, err = rsa.GenerateKey(rand.Reader, 2048) + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/api/shared/sharedfakes/fake_round_tripper.go b/api/shared/sharedfakes/fake_round_tripper.go new file mode 100644 index 00000000000..02f1ff17922 --- /dev/null +++ b/api/shared/sharedfakes/fake_round_tripper.go @@ -0,0 +1,114 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package sharedfakes + +import ( + "net/http" + "sync" +) + +type FakeRoundTripper struct { + RoundTripStub func(*http.Request) (*http.Response, error) + roundTripMutex sync.RWMutex + roundTripArgsForCall []struct { + arg1 *http.Request + } + roundTripReturns struct { + result1 *http.Response + result2 error + } + roundTripReturnsOnCall map[int]struct { + result1 *http.Response + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeRoundTripper) RoundTrip(arg1 *http.Request) (*http.Response, error) { + fake.roundTripMutex.Lock() + ret, specificReturn := fake.roundTripReturnsOnCall[len(fake.roundTripArgsForCall)] + fake.roundTripArgsForCall = append(fake.roundTripArgsForCall, struct { + arg1 *http.Request + }{arg1}) + fake.recordInvocation("RoundTrip", []interface{}{arg1}) + fake.roundTripMutex.Unlock() + if fake.RoundTripStub != nil { + return fake.RoundTripStub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.roundTripReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeRoundTripper) RoundTripCallCount() int { + fake.roundTripMutex.RLock() + defer fake.roundTripMutex.RUnlock() + return len(fake.roundTripArgsForCall) +} + +func (fake *FakeRoundTripper) RoundTripCalls(stub func(*http.Request) (*http.Response, error)) { + fake.roundTripMutex.Lock() + defer fake.roundTripMutex.Unlock() + fake.RoundTripStub = stub +} + +func (fake *FakeRoundTripper) RoundTripArgsForCall(i int) *http.Request { + fake.roundTripMutex.RLock() + defer fake.roundTripMutex.RUnlock() + argsForCall := fake.roundTripArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeRoundTripper) RoundTripReturns(result1 *http.Response, result2 error) { + fake.roundTripMutex.Lock() + defer fake.roundTripMutex.Unlock() + fake.RoundTripStub = nil + fake.roundTripReturns = struct { + result1 *http.Response + result2 error + }{result1, result2} +} + +func (fake *FakeRoundTripper) RoundTripReturnsOnCall(i int, result1 *http.Response, result2 error) { + fake.roundTripMutex.Lock() + defer fake.roundTripMutex.Unlock() + fake.RoundTripStub = nil + if fake.roundTripReturnsOnCall == nil { + fake.roundTripReturnsOnCall = make(map[int]struct { + result1 *http.Response + result2 error + }) + } + fake.roundTripReturnsOnCall[i] = struct { + result1 *http.Response + result2 error + }{result1, result2} +} + +func (fake *FakeRoundTripper) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.roundTripMutex.RLock() + defer fake.roundTripMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeRoundTripper) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ http.RoundTripper = new(FakeRoundTripper) diff --git a/api/shared/wrap_for_cf_on_k8s.go b/api/shared/wrap_for_cf_on_k8s.go new file mode 100644 index 00000000000..d5734d6ffd5 --- /dev/null +++ b/api/shared/wrap_for_cf_on_k8s.go @@ -0,0 +1,124 @@ +package shared + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "net/http" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/transport" + + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/command" + + // imported for the side effects + _ "k8s.io/client-go/plugin/pkg/client/auth/azure" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" +) + +//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 net/http.RoundTripper + +func WrapForCFOnK8sAuth(config command.Config, k8sConfigGetter v7action.KubernetesConfigGetter, roundTripper http.RoundTripper) (http.RoundTripper, error) { + username, err := config.CurrentUserName() + if err != nil { + return nil, err + } + if username == "" { + return nil, errors.New("current user not set") + } + + k8sConfig, err := k8sConfigGetter.Get() + if err != nil { + return nil, err + } + + restConfig, err := clientcmd.NewDefaultClientConfig( + *k8sConfig, + &clientcmd.ConfigOverrides{ + Context: api.Context{AuthInfo: username}, + }, + ).ClientConfig() + if err != nil { + return nil, err + } + + // Special case for certs, since we don't want mtls + cert, err := getCert(restConfig) + if err != nil { + return nil, err + } + + transportConfig, err := restConfig.TransportConfig() + if err != nil { + return nil, fmt.Errorf("failed to get transport config: %w", err) + } + + if cert != nil { + return certRoundTripper{ + cert: cert, + roundTripper: roundTripper, + }, nil + } + + if transportConfig.WrapTransport == nil { + // i.e. not auth-provider or exec plugin + return transport.HTTPWrappersForConfig(transportConfig, roundTripper) + } + + // using auth provider to generate token + return transportConfig.WrapTransport(roundTripper), nil +} + +func getCert(restConfig *rest.Config) (*tls.Certificate, error) { + tlsConfig, err := rest.TLSConfigFor(restConfig) + if err != nil { + return nil, fmt.Errorf("failed to get tls config: %w", err) + } + + if tlsConfig != nil && tlsConfig.GetClientCertificate != nil { + cert, err := tlsConfig.GetClientCertificate(nil) + if err != nil { + return nil, fmt.Errorf("failed to get client certificate: %w", err) + } + + if len(cert.Certificate) > 0 && cert.PrivateKey != nil { + return cert, nil + } + } + return nil, nil +} + +type certRoundTripper struct { + cert *tls.Certificate + roundTripper http.RoundTripper +} + +func (rt certRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + var buf bytes.Buffer + + if err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: rt.cert.Certificate[0]}); err != nil { + return nil, fmt.Errorf("could not convert certificate to PEM format: %w", err) + } + + key, err := x509.MarshalPKCS8PrivateKey(rt.cert.PrivateKey) + if err != nil { + return nil, fmt.Errorf("could not marshal private key: %w", err) + } + + if err := pem.Encode(&buf, &pem.Block{Type: "PRIVATE KEY", Bytes: key}); err != nil { + return nil, fmt.Errorf("could not convert key to PEM format: %w", err) + } + + auth := "ClientCert " + base64.StdEncoding.EncodeToString(buf.Bytes()) + req.Header.Set("Authorization", auth) + + return rt.roundTripper.RoundTrip(req) +} diff --git a/api/shared/wrap_for_cf_on_k8s_test.go b/api/shared/wrap_for_cf_on_k8s_test.go new file mode 100644 index 00000000000..a0c79c89ebd --- /dev/null +++ b/api/shared/wrap_for_cf_on_k8s_test.go @@ -0,0 +1,462 @@ +package shared_test + +import ( + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "io/ioutil" + "net/http" + "os" + "strings" + "time" + + "code.cloudfoundry.org/cli/actor/v7action/v7actionfakes" + "code.cloudfoundry.org/cli/api/shared" + "code.cloudfoundry.org/cli/api/shared/sharedfakes" + "code.cloudfoundry.org/cli/command/commandfakes" + + "github.com/SermoDigital/jose/crypto" + "github.com/SermoDigital/jose/jws" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" + "k8s.io/client-go/tools/clientcmd/api" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const ( + clientCertData = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJVk9iMUFIckxNUjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TVRFd01EVXhOVEExTURsYUZ3MHlNakV3TURVeE5UQTFNVEZhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJrdWxLbS9qTTJhZWZsdjkKK00zQk9Jc2QvVXZrRTBONGhWb3hSeWRBbE0xQXhWd3REYUdzL3dmUzRzb0xuNHJENTF3UE1SRlNJaitwSzdGYQprRGdaR0x4UFhrai96UkZOTzcvU3J2RHYwVGxjYjJENzNCS21qaXArQ2hBWkpQdWhMQlY2VnlTN0pXSWhOM1lOCktyamR5TnB5MHN3SjI1TW9CbW1saUpFc3V2dCtDaEhseERqWE9KenF1U2owa1hPQVVsWUFTN1dKK09JMU9HbzQKUjcvdHdHZlFTNW9oYXpRVVlDR2lZSllYcjVRNkVKTmJOVVI0RjdpRSthY1I5Rm9GNnNKSmkrQStET1VDUFFSKwptbjQ5Zm1pcFVHSGtMc3BicTNFZ0FEME40VW5jcmIyeUJEMFNVTmdLQmJjclY1S2hybFA2SzkwNkY5NEpubzNHCm1Id1JwUUlEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JUV2VNZ1ZBRkRhbWcraDRqS3hoRUh2Q1l5egp5akFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBUUxMWWFXQTRva1M2b3ZEWjQ1Z28ybkVZdUR4MklmRXZwYnh3CkNmYkFTNDY4M3lLT3FiYVBHOEpTVGhSbkh3TWlMcVBrbGFsdkJvV2R3aFB5Vkk0d2tVNHI4Y2c0UEpxNEZwWnQKVkNUQzFPZWVwRGpTMFpVQjRwSDVIZVlNQUxqSDBqcFV3RU96djFpaEtid05IMHFoZ2pGeUNTdld5TG9oZHdzbApJWXIvV1NEZm50NlBETC84TjFpcEJJbEN5Z1JHVGdoSFhPemhHUklPWG4rYWVOR29yWm9YWm0xbHErc1hyUnc5CktNdVZhRmdhaWVjSm0vbytyemFFSG9VZjRYOERKeVNubmVTa3ViaEx6ZERNc2o5eEs1cEJpdFgvaDlQMUQrMkcKeW5rcWdJVTJSWTM0SjBRcnU4Z0syNlJVT2pOcHIvRWJHQ0dUQUxiMXJnSDM0K2NFdlE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" + clientKeyData = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQ3VTNlVxYitNelpwNSsKVy8zNHpjRTRpeDM5UytRVFEzaUZXakZISjBDVXpVREZYQzBOb2F6L0I5TGl5Z3VmaXNQblhBOHhFVklpUDZrcgpzVnFRT0JrWXZFOWVTUC9ORVUwN3Y5S3U4Ty9ST1Z4dllQdmNFcWFPS240S0VCa2srNkVzRlhwWEpMc2xZaUUzCmRnMHF1TjNJMm5MU3pBbmJreWdHYWFXSWtTeTYrMzRLRWVYRU9OYzRuT3E1S1BTUmM0QlNWZ0JMdFluNDRqVTQKYWpoSHYrM0FaOUJMbWlGck5CUmdJYUpnbGhldmxEb1FrMXMxUkhnWHVJVDVweEgwV2dYcXdrbUw0RDRNNVFJOQpCSDZhZmoxK2FLbFFZZVF1eWx1cmNTQUFQUTNoU2R5dHZiSUVQUkpRMkFvRnR5dFhrcUd1VS9vcjNUb1gzZ21lCmpjYVlmQkdsQWdNQkFBRUNnZ0VBZG80WndLM3VteTM0TFBjaDM3VUU4eE1keVFkd0VmSlk3a3dWTE5MMFNNTDgKaGNKWEd1aVlKYmtLcHh6TG55L2laV0xuS25jZnFSQW9ZQUg1R2hRdWJmYlkvY2NseURVMmxhZTdCU2Y1MkJUdQpYUXhaQks3aS85ekRjdERVYWFXSFVkY2lLbGhmdStQdHVDM2ljdWJnWlJqQjljUzRCOVVtNm9XK0JSREtuandICkduQ0lEZlNNQWt4VXdTaUwwa2NXelNpZ1BYMVN3UHcxOEZvZWgzTmJEd1VXTHhxUWZLVThydVlSTUsxYUg5M3cKcjFtbjlDWUwvd0hiVWRqcmtZMlIxTjVUR21ab2Vldm5qUXgyQVc2NkYzdEg1cGg4RTF6TEFQVTl4TFdRTW9KcwpXM0gzSTdUaEYvRnJuNERQa3hQbThUUVVhQUdvQ09SSWFUQkN5VlgxQVFLQmdRREkxbkRmNWYySHdHaldrTStpCk9YbGE1R1VnRUtXaGZpeGhidE5OclNpMDU5VnZQUEJwNXdtbGQzMHJKUDhWem8vbnFnUW5ISmpmaEQ2Y3NSMTQKL2VlMHZ1Um0zYzZwZzMrODdwOHhWY3lLNHhDd0JmdFFuMGRZWWFLMkRMOEtYb0liYThpN09EQmFoNW5OZWQwcgpKa1RPcE5NRGRkL0p0bEpPZ25jRXBlUk9oUUtCZ1FEZUt1L0R1MXU1QVR3Y3p5STRXOWV1L1YwTXRwMHdqM3RpClF2MmpObW83QU1zS3BwK0ZKVDFqWFhUKzZCTm02OWpxUVJwdlAyd2RhVUdqV1dLa1lHVEVpbUZCc2ZuKzJDOFAKOEc3Uk50YWpRdEV2QlR1ZDZPN0tZUkFoTU56dm9RcDkrZmJKY1ZsRG13Nlk0bUYzUTJXS3NmZU51TGtpY3VqNQpYVFV1ekVMd29RS0JnUUREU0IvQTFYVEx4cjhwd3V6aHBGam5sQ1R3Skwrb1kzTHIya01EeUZkSWNCUU1jWWlpCnNNK2tZS2NJaUpTdnM0WWhrQ014bEpEZzVVbXNPbHVhQmVpQ3l3cHpLMEdEZWlWK285ZU90UXFLRVhkc2NLU0oKSkJiUFRVQlZHOWUyVVdiWkd0aTNrazhSOThBSkYzR0NQMWV3Um53WFpVb1FiSU5qYTJBbTJOZEJzUUtCZ0Q4eApOVXVXTWl1NE56SDJsTVExRTI4NXI4cmE4bkVLanN6UFF6ZTJWWmI4emNQMHl2RGpPOGZVb0YrVkFWZklBOFgxCnlLQVdDUm1BZytRRG03UW5tdUh3Zm1OaVRUcDRvVUpHWUM3d0N6TWE0VWNmbE9xQWc5TmFzbXpPYWpsYXRCSkwKRkRBT0pwYTlOdlN6aDRlVnl2OGRTYzJzMmpQN1BWc1ljUFVqc25LaEFvR0JBSy9kQjlnVEFpME5nczVmaVNtWQovWkp3Yk52MjcyTHdKbWV4Vit2eWtjN3J5LzRraTRQb2xRd1BHNzQ5eFZ0T2NNc2FhRlVNMVVkclN2NlIwbjlkCmpTbXhCeTl2YWdzc1FmVDNSc3BvUUJKM0w5YWxiNHM2V2ZtUEpzNkFrQkhIZHNpVXFaaElYT2J2WE1lQ0k2aVMKOTQ2R0toekFxMlVGbjhFUGxXaFVNeEFiCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K" +) + +var _ = Describe("WrapForCFOnK8sAuth", func() { + var ( + config *commandfakes.FakeConfig + k8sConfigGetter *v7actionfakes.FakeKubernetesConfigGetter + req *http.Request + res *http.Response + actualRes *http.Response + kubeConfig *api.Config + wrapErr error + wrappedRoundTripper *sharedfakes.FakeRoundTripper + ) + + BeforeEach(func() { + kubeConfig = &api.Config{ + Kind: "Config", + APIVersion: "v1", + Clusters: map[string]*api.Cluster{ + "my-cluster": { + Server: "https://example.org", + }, + }, + Contexts: map[string]*api.Context{ + "my-context": { + Cluster: "my-cluster", + AuthInfo: "my-auth-info", + Namespace: "my-namespace", + }, + }, + CurrentContext: "my-context", + AuthInfos: map[string]*api.AuthInfo{}, + } + + k8sConfigGetter = new(v7actionfakes.FakeKubernetesConfigGetter) + k8sConfigGetter.GetReturns(kubeConfig, nil) + + config = new(commandfakes.FakeConfig) + config.CurrentUserNameReturns("auth-test", nil) + + var err error + req, err = http.NewRequest(http.MethodPost, "", strings.NewReader("hello")) + Expect(err).NotTo(HaveOccurred()) + + wrappedRoundTripper = new(sharedfakes.FakeRoundTripper) + res = &http.Response{StatusCode: http.StatusTeapot} + + wrappedRoundTripper.RoundTripReturns(res, nil) + actualRes = nil + }) + + JustBeforeEach(func() { + var roundTripper http.RoundTripper + roundTripper, wrapErr = shared.WrapForCFOnK8sAuth(config, k8sConfigGetter, wrappedRoundTripper) + + if wrapErr == nil { + actualRes, wrapErr = roundTripper.RoundTrip(req) + } + }) + + When("getting the k8s config fails", func() { + BeforeEach(func() { + k8sConfigGetter.GetReturns(nil, errors.New("boom!")) + }) + + It("returns the error", func() { + Expect(wrapErr).To(MatchError("boom!")) + }) + }) + + When("no user is set in the config", func() { + BeforeEach(func() { + config.CurrentUserNameReturns("", nil) + }) + + It("errors", func() { + Expect(wrapErr).To(MatchError(ContainSubstring("current user not set"))) + }) + }) + + When("there is an error getting the current user from the config", func() { + BeforeEach(func() { + config.CurrentUserNameReturns("", errors.New("boom")) + }) + + It("errors", func() { + Expect(wrapErr).To(MatchError(ContainSubstring("boom"))) + }) + }) + + When("the chosen kubernetes auth info is not present in kubeconfig", func() { + BeforeEach(func() { + config.CurrentUserNameReturns("not-present", nil) + }) + + It("errors", func() { + Expect(wrapErr).To(MatchError(ContainSubstring(`auth info "not-present" does not exist`))) + }) + }) + + checkCalls := func() *http.Request { + Expect(wrapErr).NotTo(HaveOccurred()) + Expect(wrappedRoundTripper.RoundTripCallCount()).To(Equal(1)) + + actualReq := wrappedRoundTripper.RoundTripArgsForCall(0) + + body, err := ioutil.ReadAll(actualReq.Body) + Expect(err).NotTo(HaveOccurred()) + Expect(string(body)).To(Equal("hello")) + + Expect(actualRes).To(Equal(res)) + + return actualReq + } + + checkBearerTokenInAuthHeader := func() { + actualReq := checkCalls() + + token, err := jws.ParseJWTFromRequest(actualReq) + Expect(err).NotTo(HaveOccurred()) + Expect(token.Validate(keyPair.Public(), crypto.SigningMethodRS256)).To(Succeed()) + + claims := token.Claims() + Expect(claims).To(HaveKeyWithValue("another", "thing")) + } + + checkClientCertInAuthHeader := func() { + actualReq := checkCalls() + + Expect(actualReq.Header).To(HaveKeyWithValue("Authorization", ConsistOf(HavePrefix("ClientCert ")))) + + certAndKeyPEMBase64 := actualReq.Header.Get("Authorization")[11:] + certAndKeyPEM, err := base64.StdEncoding.DecodeString(certAndKeyPEMBase64) + Expect(err).NotTo(HaveOccurred()) + + cert, rest := pem.Decode(certAndKeyPEM) + Expect(cert.Type).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Type)) + Expect(cert.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Bytes)) + + var key *pem.Block + key, rest = pem.Decode(rest) + Expect(key.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientKeyData).Bytes)) + + Expect(rest).To(BeEmpty()) + } + + Describe("auth-provider", func() { + var token []byte + + BeforeEach(func() { + jwt := jws.NewJWT(jws.Claims{ + "exp": time.Now().Add(time.Hour).Unix(), + "another": "thing", + }, crypto.SigningMethodRS256) + var err error + token, err = jwt.Serialize(keyPair) + Expect(err).NotTo(HaveOccurred()) + + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + AuthProvider: &api.AuthProviderConfig{ + Name: "oidc", + Config: map[string]string{ + "id-token": string(token), + "idp-issuer-url": "-", + "client-id": "-", + }, + }, + } + }) + + It("uses the auth-provider to generate the Bearer token", func() { + checkBearerTokenInAuthHeader() + }) + }) + + Describe("client certs", func() { + var ( + certFilePath string + keyFilePath string + ) + + BeforeEach(func() { + certFilePath = writeToFile(clientCertData) + keyFilePath = writeToFile(clientKeyData) + }) + + AfterEach(func() { + Expect(os.RemoveAll(certFilePath)).To(Succeed()) + Expect(os.RemoveAll(keyFilePath)).To(Succeed()) + }) + + When("inline cert and key are provided", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + ClientCertificateData: []byte(base64Decode(clientCertData)), + ClientKeyData: []byte(base64Decode(clientKeyData)), + } + }) + + It("puts concatenated client ceritificate and key data into the Authorization header", func() { + checkClientCertInAuthHeader() + }) + }) + + When("cert and key are provided in files", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + ClientCertificate: certFilePath, + ClientKey: keyFilePath, + } + }) + + It("puts concatenated client ceritificate and key data into the Authorization header", func() { + checkClientCertInAuthHeader() + }) + + When("cert file cannot be read", func() { + BeforeEach(func() { + Expect(os.Remove(certFilePath)).To(Succeed()) + }) + + It("returns an error", func() { + Expect(wrapErr).To(MatchError(ContainSubstring(certFilePath))) + }) + }) + + When("key file cannot be read", func() { + BeforeEach(func() { + Expect(os.Remove(keyFilePath)).To(Succeed()) + }) + + It("returns an error", func() { + Expect(wrapErr).To(MatchError(ContainSubstring(keyFilePath))) + }) + }) + }) + + When("file and inline cert is provided", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + ClientCertificate: certFilePath, + ClientCertificateData: []byte(base64Decode(clientCertData)), + ClientKeyData: []byte(base64Decode(clientKeyData)), + } + }) + + It("complains about invalid configuration", func() { + Expect(wrapErr).To(MatchError(ContainSubstring("client-cert-data and client-cert are both specified"))) + }) + }) + + When("file and inline key is provided", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + ClientCertificateData: []byte(base64Decode(clientCertData)), + ClientKeyData: []byte(base64Decode(clientKeyData)), + ClientKey: keyFilePath, + } + }) + + It("complains about invalid configuration", func() { + Expect(wrapErr).To(MatchError(ContainSubstring("client-key-data and client-key are both specified"))) + }) + }) + + When("inline cert and key file are provided", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + ClientCertificateData: []byte(base64Decode(clientCertData)), + ClientKey: keyFilePath, + } + }) + + It("uses the inline key", func() { + checkClientCertInAuthHeader() + }) + }) + + When("cert file and inline key are provided", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + ClientCertificate: certFilePath, + ClientKeyData: []byte(base64Decode(clientKeyData)), + } + }) + + It("uses the inline key", func() { + checkClientCertInAuthHeader() + }) + }) + }) + + Describe("exec", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + Exec: &api.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1beta1", + InteractiveMode: "Never", + Command: "echo", + }, + } + }) + + When("the command returns a token", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"].Exec.Args = []string{execCredential(&clientauthenticationv1beta1.ExecCredentialStatus{ + Token: "a-token", + })} + }) + + It("uses the exec command to generate the Bearer token", func() { + Expect(wrapErr).NotTo(HaveOccurred()) + Expect(wrappedRoundTripper.RoundTripCallCount()).To(Equal(1)) + + actualReq := wrappedRoundTripper.RoundTripArgsForCall(0) + Expect(actualReq.Header.Get("Authorization")).To(Equal("Bearer a-token")) + + Expect(actualRes).To(Equal(res)) + }) + }) + + When("the command returns a client cert and key", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"].Exec.Args = []string{execCredential(&clientauthenticationv1beta1.ExecCredentialStatus{ + ClientCertificateData: base64Decode(clientCertData), + ClientKeyData: base64Decode(clientKeyData), + })} + }) + + It("uses the exec command to generate client certs", func() { + checkClientCertInAuthHeader() + }) + }) + }) + + Describe("tokens provided in config", func() { + var token []byte + + BeforeEach(func() { + jwt := jws.NewJWT(jws.Claims{ + "exp": time.Now().Add(time.Hour).Unix(), + "another": "thing", + }, crypto.SigningMethodRS256) + var err error + token, err = jwt.Serialize(keyPair) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("inline tokens", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + Token: string(token), + } + }) + + It("inserts the token in the authorization header", func() { + checkBearerTokenInAuthHeader() + }) + }) + + Context("token file paths", func() { + var tokenFilePath string + + BeforeEach(func() { + tokenFile, err := ioutil.TempFile("", "") + Expect(err).NotTo(HaveOccurred()) + defer tokenFile.Close() + _, err = tokenFile.Write(token) + Expect(err).NotTo(HaveOccurred()) + tokenFilePath = tokenFile.Name() + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + TokenFile: tokenFilePath, + } + }) + + AfterEach(func() { + Expect(os.RemoveAll(tokenFilePath)).To(Succeed()) + }) + + It("inserts the token in the authorization header", func() { + checkBearerTokenInAuthHeader() + }) + }) + + When("both file and inline token are provided", func() { + BeforeEach(func() { + kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ + Token: string(token), + TokenFile: "some-path", + } + }) + + It("the inline token takes precedence", func() { + checkBearerTokenInAuthHeader() + }) + }) + }) +}) + +func pemDecodeKubeConfigCertData(data string) *pem.Block { + decodedData, err := base64.StdEncoding.DecodeString(data) + Expect(err).NotTo(HaveOccurred()) + pemDecodedBlock, rest := pem.Decode(decodedData) + Expect(rest).To(BeEmpty()) + return pemDecodedBlock +} + +func base64Decode(encoded string) string { + decoded, err := base64.StdEncoding.DecodeString(encoded) + Expect(err).NotTo(HaveOccurred()) + return string(decoded) +} + +func writeToFile(base64Data string) string { + file, err := ioutil.TempFile("", "") + Expect(err).NotTo(HaveOccurred()) + file.WriteString(base64Decode(base64Data)) + Expect(file.Close()).To(Succeed()) + return file.Name() +} + +func execCredential(status *clientauthenticationv1beta1.ExecCredentialStatus) string { + execCred, err := json.Marshal(clientauthenticationv1beta1.ExecCredential{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "client.authentication.k8s.io/v1beta1", + }, + Status: status, + }) + Expect(err).NotTo(HaveOccurred()) + return string(execCred) +} diff --git a/cf/api/repository_locator.go b/cf/api/repository_locator.go index ef80f3d7cc7..f0ad31cb1db 100644 --- a/cf/api/repository_locator.go +++ b/cf/api/repository_locator.go @@ -2,6 +2,8 @@ package api import ( "code.cloudfoundry.org/cli/actor/sharedaction" + v7action2 "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/cf/api/appevents" api_appfiles "code.cloudfoundry.org/cli/cf/api/appfiles" "code.cloudfoundry.org/cli/cf/api/appinstances" @@ -26,7 +28,6 @@ import ( "code.cloudfoundry.org/cli/cf/configuration/coreconfig" "code.cloudfoundry.org/cli/cf/net" "code.cloudfoundry.org/cli/cf/trace" - "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/util/configv3" ) @@ -100,7 +101,11 @@ func NewRepositoryLocator(config coreconfig.ReadWriter, gatewaysByName map[strin } logCacheURL := configV3.ConfigFile.LogCacheEndpoint - logCacheClient := command.NewLogCacheClient(logCacheURL, configV3, nil) + logCacheClient, err := logcache.NewClient(logCacheURL, configV3, nil, v7action2.NewDefaultKubernetesConfigGetter()) + if err != nil { + panic("handle this error!") + } + loc.logsRepo = logs.NewLogCacheRepository(logCacheClient, sharedaction.GetRecentLogs, sharedaction.GetStreamingLogs) loc.organizationRepo = organizations.NewCloudControllerOrganizationRepository(config, cloudControllerGateway) diff --git a/command/v7/copy_source_command.go b/command/v7/copy_source_command.go index 42f869b2458..a5c74ae0ea4 100644 --- a/command/v7/copy_source_command.go +++ b/command/v7/copy_source_command.go @@ -1,7 +1,9 @@ package v7 import ( + "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" @@ -55,7 +57,11 @@ func (cmd *CopySourceCommand) Setup(config command.Config, ui command.UI) error return err } - logCacheClient := command.NewLogCacheClient(config.LogCacheEndpoint(), config, ui) + logCacheClient, err := logcache.NewClient(config.LogCacheEndpoint(), config, ui, v7action.NewDefaultKubernetesConfigGetter()) + if err != nil { + return err + } + cmd.Stager = shared.NewAppStager(cmd.Actor, cmd.UI, cmd.Config, logCacheClient) return nil diff --git a/command/v7/logs_command.go b/command/v7/logs_command.go index e5232665010..b2506240752 100644 --- a/command/v7/logs_command.go +++ b/command/v7/logs_command.go @@ -7,6 +7,8 @@ import ( "code.cloudfoundry.org/cli/actor/actionerror" "code.cloudfoundry.org/cli/actor/sharedaction" + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" ) @@ -28,8 +30,8 @@ func (cmd *LogsCommand) Setup(config command.Config, ui command.UI) error { return err } - cmd.LogCacheClient = command.NewLogCacheClient(config.LogCacheEndpoint(), config, ui) - return nil + cmd.LogCacheClient, err = logcache.NewClient(config.LogCacheEndpoint(), config, ui, v7action.NewDefaultKubernetesConfigGetter()) + return err } func (cmd LogsCommand) Execute(args []string) error { diff --git a/command/v7/push_command.go b/command/v7/push_command.go index c7becf02fef..ea2e9175e48 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -6,12 +6,17 @@ import ( "os" "strings" + "github.com/cloudfoundry/bosh-cli/director/template" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" + "code.cloudfoundry.org/cli/actor/actionerror" "code.cloudfoundry.org/cli/actor/sharedaction" "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/actor/v7pushaction" "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/cf/errors" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" @@ -21,9 +26,6 @@ import ( "code.cloudfoundry.org/cli/util/configv3" "code.cloudfoundry.org/cli/util/manifestparser" "code.cloudfoundry.org/cli/util/progressbar" - "github.com/cloudfoundry/bosh-cli/director/template" - log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" ) //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . ProgressBar @@ -127,7 +129,10 @@ func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error { cmd.VersionActor = cmd.Actor cmd.PushActor = v7pushaction.NewActor(cmd.Actor, sharedaction.NewActor(config)) - cmd.LogCacheClient = command.NewLogCacheClient(config.LogCacheEndpoint(), config, ui) + cmd.LogCacheClient, err = logcache.NewClient(config.LogCacheEndpoint(), config, ui, v7action.NewDefaultKubernetesConfigGetter()) + if err != nil { + return err + } currentDir, err := os.Getwd() cmd.CWD = currentDir diff --git a/command/v7/restage_command.go b/command/v7/restage_command.go index 628bd852130..e9dc01ced53 100644 --- a/command/v7/restage_command.go +++ b/command/v7/restage_command.go @@ -2,7 +2,9 @@ package v7 import ( "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" @@ -29,7 +31,11 @@ func (cmd *RestageCommand) Setup(config command.Config, ui command.UI) error { return err } - logCacheClient := command.NewLogCacheClient(config.LogCacheEndpoint(), config, ui) + logCacheClient, err := logcache.NewClient(config.LogCacheEndpoint(), config, ui, v7action.NewDefaultKubernetesConfigGetter()) + if err != nil { + return err + } + cmd.Stager = shared.NewAppStager(cmd.Actor, cmd.UI, cmd.Config, logCacheClient) return nil diff --git a/command/v7/restart_command.go b/command/v7/restart_command.go index 66652cc0cf8..71b7774e796 100644 --- a/command/v7/restart_command.go +++ b/command/v7/restart_command.go @@ -1,7 +1,9 @@ package v7 import ( + "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/v7/shared" @@ -26,7 +28,12 @@ func (cmd *RestartCommand) Setup(config command.Config, ui command.UI) error { if err != nil { return err } - logCacheClient := command.NewLogCacheClient(config.LogCacheEndpoint(), config, ui) + + logCacheClient, err := logcache.NewClient(config.LogCacheEndpoint(), config, ui, v7action.NewDefaultKubernetesConfigGetter()) + if err != nil { + return err + } + cmd.Stager = shared.NewAppStager(cmd.Actor, cmd.UI, cmd.Config, logCacheClient) return nil diff --git a/command/v7/stage_package_command.go b/command/v7/stage_package_command.go index 729f497461b..3406a444c33 100644 --- a/command/v7/stage_package_command.go +++ b/command/v7/stage_package_command.go @@ -5,6 +5,8 @@ import ( "time" "code.cloudfoundry.org/cli/actor/sharedaction" + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/v7/shared" @@ -30,7 +32,10 @@ func (cmd *StagePackageCommand) Setup(config command.Config, ui command.UI) erro return err } - cmd.LogCacheClient = command.NewLogCacheClient(config.LogCacheEndpoint(), config, ui) + cmd.LogCacheClient, err = logcache.NewClient(config.LogCacheEndpoint(), config, ui, v7action.NewDefaultKubernetesConfigGetter()) + if err != nil { + return err + } return nil } diff --git a/command/v7/start_command.go b/command/v7/start_command.go index 23271215110..823341ff29c 100644 --- a/command/v7/start_command.go +++ b/command/v7/start_command.go @@ -2,7 +2,9 @@ package v7 import ( "code.cloudfoundry.org/cli/actor/sharedaction" + "code.cloudfoundry.org/cli/actor/v7action" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/api/logcache" "code.cloudfoundry.org/cli/command" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/v7/shared" @@ -27,7 +29,11 @@ func (cmd *StartCommand) Setup(config command.Config, ui command.UI) error { return err } - cmd.LogCacheClient = command.NewLogCacheClient(config.LogCacheEndpoint(), config, ui) + cmd.LogCacheClient, err = logcache.NewClient(config.LogCacheEndpoint(), config, ui, v7action.NewDefaultKubernetesConfigGetter()) + if err != nil { + return err + } + cmd.Stager = shared.NewAppStager(cmd.Actor, cmd.UI, cmd.Config, cmd.LogCacheClient) return nil diff --git a/integration/v7/selfcontained/kubernetes_auth_log_test.go b/integration/v7/selfcontained/kubernetes_auth_log_test.go new file mode 100644 index 00000000000..8d2d244c4c3 --- /dev/null +++ b/integration/v7/selfcontained/kubernetes_auth_log_test.go @@ -0,0 +1,138 @@ +package selfcontained_test + +import ( + "net/http" + "path/filepath" + + "code.cloudfoundry.org/cli/integration/helpers" + "code.cloudfoundry.org/cli/integration/v7/selfcontained/fake" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" + apiv1 "k8s.io/client-go/tools/clientcmd/api/v1" + + "code.cloudfoundry.org/cli/util/configv3" +) + +var _ = Describe("logclient auth-provider", func() { + var ( + apiConfig fake.CFAPIConfig + kubeConfig apiv1.Config + ) + + BeforeEach(func() { + apiConfig = fake.CFAPIConfig{ + Routes: map[string]fake.Response{ + "GET /api/v1/read/test-guid": { + Code: http.StatusOK, Body: map[string]interface{}{ + "envelopes": map[string]interface{}{ + "batch": []string{}, + }, + }, + }, + "GET /api/v1/info": { + Code: http.StatusOK, Body: map[string]interface{}{ + "version": "42.1.2", + "vm_uptime": "0", + }, + }, + "GET /v3/apps": { + Code: http.StatusOK, Body: map[string]interface{}{ + "pagination": map[string]interface{}{}, + "resources": []map[string]string{ + { + "guid": "test-guid", + }, + }, + }, + }, + "GET /whoami": { + Code: http.StatusOK, Body: map[string]interface{}{ + "name": "my-user", + "kind": "User", + }, + }, + }, + } + apiServer.SetConfiguration(apiConfig) + helpers.SetConfig(func(config *configv3.Config) { + config.ConfigFile.Target = apiServer.URL() + config.ConfigFile.LogCacheEndpoint = apiServer.URL() + config.ConfigFile.CFOnK8s.Enabled = true + config.ConfigFile.CFOnK8s.AuthInfo = "my-user" + config.ConfigFile.TargetedOrganization = configv3.Organization{ + GUID: "my-org", + Name: "My Org", + } + + config.ConfigFile.TargetedSpace = configv3.Space{ + GUID: "my-space", + Name: "My Space", + } + }) + + kubeConfig = apiv1.Config{ + Kind: "Config", + APIVersion: "v1", + AuthInfos: []apiv1.NamedAuthInfo{ + { + Name: "my-user", + AuthInfo: apiv1.AuthInfo{ + AuthProvider: &apiv1.AuthProviderConfig{ + Name: "oidc", + Config: map[string]string{ + "id-token": string(token), + "idp-issuer-url": "-", + "client-id": "-", + }, + }, + }, + }, + }, + Clusters: []apiv1.NamedCluster{ + { + Name: "my-cluster", + Cluster: apiv1.Cluster{ + Server: "https://example.org", + }, + }, + }, + Contexts: []apiv1.NamedContext{ + { + Name: "my-context", + Context: apiv1.Context{ + Cluster: "my-cluster", + AuthInfo: "my-auth-info", + Namespace: "my-namespace", + }, + }, + }, + CurrentContext: "my-context", + } + kubeConfigPath := filepath.Join(homeDir, ".kube", "config") + storeKubeConfig(kubeConfig, kubeConfigPath) + + env = helpers.CFEnv{ + EnvVars: map[string]string{ + "KUBECONFIG": kubeConfigPath, + }, + } + }) + + JustBeforeEach(func() { + Eventually(helpers.CustomCF(env, "logs", "--recent", "my-test-app")).Should(gexec.Exit(0)) + }) + + It("sends the Bearer token in the Authorization header", func() { + reqs := apiServer.ReceivedRequests()["GET /api/v1/read/test-guid"] + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Header).To(HaveKeyWithValue("Authorization", ConsistOf("Bearer "+string(token)))) + }) + + It("sends the User-Agent header", func() { + reqs := apiServer.ReceivedRequests()["GET /api/v1/read/test-guid"] + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Header).To(HaveKeyWithValue("User-Agent", ConsistOf(ContainSubstring("cf")))) + }) +}) From d3bb1ecfc94ee12432f8defb30ab9459e83cc727 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Feb 2022 21:10:14 +0000 Subject: [PATCH 009/543] Bump github.com/docker/distribution Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.6.0-rc.1.0.20171109224904-e5b5e44386f7+incompatible to 2.8.0+incompatible. - [Release notes](https://github.com/docker/distribution/releases) - [Commits](https://github.com/docker/distribution/commits/v2.8.0) --- updated-dependencies: - dependency-name: github.com/docker/distribution dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fd09459e347..e1d57e6a039 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/cloudfoundry/bosh-cli v5.5.1+incompatible github.com/cyphar/filepath-securejoin v0.2.1 - github.com/docker/distribution v2.6.0-rc.1.0.20171109224904-e5b5e44386f7+incompatible + github.com/docker/distribution v2.8.0+incompatible github.com/fatih/color v1.5.1-0.20170926111411-5df930a27be2 github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 github.com/jessevdk/go-flags v1.4.1-0.20181221193153-c0795c8afcf4 diff --git a/go.sum b/go.sum index 6ee6cbe53b0..46324f18f2e 100644 --- a/go.sum +++ b/go.sum @@ -127,8 +127,8 @@ github.com/cyphar/filepath-securejoin v0.2.1/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/distribution v2.6.0-rc.1.0.20171109224904-e5b5e44386f7+incompatible h1:AJ5ylGRVLdpWcAw14qENVOr/M8/s7vApzMorVhyVL78= -github.com/docker/distribution v2.6.0-rc.1.0.20171109224904-e5b5e44386f7+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY= +github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20171120205147-9de84a78d76e h1:M+/1NNHE/mg+RUox/04+rZoahJVklPfs6xZFECVnxso= github.com/docker/docker v1.4.2-0.20171120205147-9de84a78d76e/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= From b04b29dabc470cf9d9858e6c471e996064451b87 Mon Sep 17 00:00:00 2001 From: Al Berez Date: Tue, 19 Apr 2022 11:49:06 -0700 Subject: [PATCH 010/543] [v8] Online release process with Github Actions (#2265) * Add Build-Sign-Release workflow Commit summary below Current state of CF CLI release infrastructure is offline and quite outdated. To make our release process more transparent and inclusive we decided to move build, signing and release processes online to Github Actions. * Split out release/upload part of workflow * Add capabilities to release workflow At this point working in a workflow separate from build, for separation of concerns between building&signing, and releasing. This is a squashed commit of the gha-allplatforms branch, consisting of the following changes : - Upload mac installer to S3 - include mac installer in github release - retrieve mac binaries in release job - add write permission to github release job - Add linux packages, and simplify CLAW upload steps - Switch to sync action to support simpler CLAW S3 upload step - Add linux packages to GH Release - Update artifact names in release jobs - aws s3 ls to debug s3 upload - bump aws s3 sync to non-default-region change * Updated for build, release, and units workflows * Add update-repos workflow Update-repos workflow is in charge of updating and signing on a repository level. This workflow should be triggered towards the end of the release process after all packages are available via distribution network (after CLAW update). It will update the following repositories: - Homebrew formula in homebrew-tap git repository - RPM repo hosted on S3 - Debian repo hosted on S3 Current implementations of the update-repos workflow is matching offline release process. Following updates are recommended: - Implement backup and disaster recovery procedure - Debian - fill license and vendor fields - RPM - sign on the repo level - Homebrew - add arm architecture * Release pipelines updates * Removes/Cleans comments Also, small change to sign-windows-binary.ps1 as that step was already being done in the sign job * Update github actions secrets template for use with the set-github-actions-env-vars.sh script, or the github cli's 'secret set' subcommand directly Co-authored-by: Alexander Berezovsky Co-authored-by: George Gelashvili Co-authored-by: Juan Diego Gonzalez Signed-off-by: Pete Levine --- .github/license/CF_NOTICE | 36 + .../license/LICENSE-WITH-3RD-PARTY-LICENSES | 1149 +++++++++++++++++ .github/release/gon.json | 14 + .github/secrets.template.envrc | 29 + .github/win/cf.ico | Bin 0 -> 13094 bytes .github/win/common.iss | 76 ++ .github/win/innosetup/Dockerfile | 19 + .github/win/install-innosetup.ps1 | 18 + .github/win/run-innosetup.ps1 | 20 + .github/win/sign-windows-binary.ps1 | 9 + .github/win/windows-installer-v8-x64.iss | 51 + .github/win/windows-installer-v8-x86.iss | 31 + .github/workflows/build-sign-upload.yml | 880 +++++++++++++ .github/workflows/code-quality.yml | 3 + .github/workflows/units.yml | 133 +- .github/workflows/update-repos.yml | 416 ++++++ .gitignore | 2 + Makefile | 17 +- bin/bump-version | 19 +- bin/generate-release-notes | 2 +- 20 files changed, 2846 insertions(+), 78 deletions(-) create mode 100644 .github/license/CF_NOTICE create mode 100644 .github/license/LICENSE-WITH-3RD-PARTY-LICENSES create mode 100644 .github/release/gon.json create mode 100644 .github/secrets.template.envrc create mode 100644 .github/win/cf.ico create mode 100644 .github/win/common.iss create mode 100644 .github/win/innosetup/Dockerfile create mode 100644 .github/win/install-innosetup.ps1 create mode 100644 .github/win/run-innosetup.ps1 create mode 100644 .github/win/sign-windows-binary.ps1 create mode 100644 .github/win/windows-installer-v8-x64.iss create mode 100644 .github/win/windows-installer-v8-x86.iss create mode 100644 .github/workflows/build-sign-upload.yml create mode 100644 .github/workflows/update-repos.yml diff --git a/.github/license/CF_NOTICE b/.github/license/CF_NOTICE new file mode 100644 index 00000000000..2224b281c8e --- /dev/null +++ b/.github/license/CF_NOTICE @@ -0,0 +1,36 @@ +Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. + +This product contains software that is Copyright (c) 2013-2015 Pivotal Software, Inc. + +This product 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. + + +Attribution notices: + +This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/code.cloudfoundry.org/gofileutils/fileutils that is: +Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. +Copyright (c) 2014-2015 Pivotal Software, Inc. +and is licensed under the Apache License, Version 2.0. + +This product includes software from https://github.com/code.cloudfoundry.org/cli/tree/master/vendor/code.cloudfoundry.org/ykk that is: +Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. +and is licensed under the Apache License, Version 2.0. + +This product includes software from https://github.com/code.cloudfoundry.org/cli/tree/master/vendor/code.cloudfoundry.org/cfnetworking-cli-api that is: +Copyright (c) 2016-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. +and is licensed under the Apache License, Version 2.0. + +This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/github.com/cloudfoundry/cli-plugin-repo/web that is: +Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. +Copyright (c) 2015 Pivotal Software, Inc. +and is licensed under the Apache License, Version 2.0. + +This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/github.com/cloudfoundry/dropsonde that is: +Copyright (c) 2014-2015 Pivotal Software, Inc. +and is licensed under the Apache License, Version 2.0. + +This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/github.com/docker/docker/pkg/term that is: +Copyright 2012-2016 Docker, Inc. +and is licensed under the Apache License, Version 2.0. diff --git a/.github/license/LICENSE-WITH-3RD-PARTY-LICENSES b/.github/license/LICENSE-WITH-3RD-PARTY-LICENSES new file mode 100644 index 00000000000..d8ce5236b07 --- /dev/null +++ b/.github/license/LICENSE-WITH-3RD-PARTY-LICENSES @@ -0,0 +1,1149 @@ + + 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. + + +================ + +This product includes software with separate copyright notices and +license terms, as noted below. + + +For vendor/golang.org/x/sys/unix: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/golang.org/x/sys/windows: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/golang.org/x/net/websocket: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/golang.org/x/crypto: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/blang/semver: + +The MIT License + +Copyright (c) 2014 Benedikt Lang + +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. + + +For vendor/github.com/tedsuo/rata: + +The MIT License (MIT) + +Copyright (c) 2014 Ted Young + +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. + + +For vendor/github.com/golang/protobuf/ptypes/any: + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/golang/protobuf/proto: + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/jessevdk/go-flags: + +Copyright (c) 2012 Jesse van den Kieboom. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +For vendor/github.com/SermoDigital/jose: + +The MIT License (MIT) + +Copyright (c) 2015 Sermo Digital LLC + +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. + + +For vendor/github.com/Azure/go-ansiterm: + +The MIT License (MIT) + +Copyright (c) 2015 Microsoft Corporation + +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. + + +For vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor: + +Extensions for Protocol Buffers to create more go like structures. + +Copyright (c) 2013, Vastech SA (PTY) LTD. All rights reserved. +http://github.com/gogo/protobuf/gogoproto + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/gogo/protobuf/gogoproto: + +Protocol Buffers for Go with Gadgets + +Copyright (c) 2013, The GoGo Authors. All rights reserved. +http://github.com/gogo/protobuf + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/gogo/protobuf/proto: + +Protocol Buffers for Go with Gadgets + +Copyright (c) 2013, The GoGo Authors. All rights reserved. +http://github.com/gogo/protobuf + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/google/go-querystring/query: + +Copyright (c) 2013 Google. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/mattn/go-colorable: + +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +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. + + +For vendor/github.com/mattn/go-isatty: + +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +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. + + +For vendor/github.com/mattn/go-runewidth: + +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +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. + + +For vendor/github.com/sajari/fuzzy: + +The MIT License (MIT) + +Copyright (c) 2014 Sajari Pty Ltd + +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. + + +For vendor/github.com/gorilla/websocket: + +Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/sirupsen/logrus: + +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +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. + + +For vendor/github.com/lunixbochs/vtclean: + +The MIT License (MIT) + +Copyright (c) 2015 Ryan Hileman + +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. + +For vendor/gopkg.in/cheggaaa/pb.v1: + +Copyright (c) 2012-2015, Sergey Cherepanov All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of its contributors may be used +* to endorse or promote products derived from this software without specific +* prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +For vendor/github.com/sabhiram/go-gitignore: + +The MIT License (MIT) + +Copyright (c) 2015 Shaba Abhiram + +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. + +For portions of vendor/gopkg.in/yaml.v2: + +Copyright (c) 2006 Kirill Simonov + +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. + + +For vendor/github.com/bmatcuk/doublestar: + +The MIT License (MIT) + +Copyright (c) 2014 Bob Matcuk + +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. + + +For vendor/github.com/bmizerany/pat: + +Copyright (C) 2012 by Keith Rarick, Blake Mizerany + +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. + + +For vendor/github.com/charlievieth/fs: + +The MIT License (MIT) + +Copyright (c) 2016 Charlie Vieth + +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. + + +For vendor/github.com/cppforlife/go-patch: + +Copyright (c) 2016 Dmitriy Kalinin + +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. + + +For vendor/github.com/fatih/color: + +The MIT License (MIT) + +Copyright (c) 2013 Fatih Arslan + +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. + + +For vendor/github.com/mailru/easyjson: + +Copyright (c) 2016 Mail.Ru Group + +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. + + +For vendor/github.com/vito/go-interact: + +Copyright (c) 2015-2016 Alex Suraci + +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. + + +For vendor/github.com/pkg/errors: + +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/github.com/cyphar/filepath-securejoin: + +Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +Copyright (C) 2017 SUSE LLC. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +For vendor/golang.org/x/text: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.github/release/gon.json b/.github/release/gon.json new file mode 100644 index 00000000000..73fd4923a2a --- /dev/null +++ b/.github/release/gon.json @@ -0,0 +1,14 @@ +{ + "source" : [ + "./dist/hello-darwin_darwin_amd64/hello", + "./dist/hello-darwin_darwin_arm64/hello" + ], + "bundle_id" : "com.example.hello", + "apple_id": { + "username" : "none@example.com", + "password": "@env:AC_PASSWORD" + }, + "sign" :{ + "application_identity" : "test-sign" + } +} diff --git a/.github/secrets.template.envrc b/.github/secrets.template.envrc new file mode 100644 index 00000000000..0e955f6f089 --- /dev/null +++ b/.github/secrets.template.envrc @@ -0,0 +1,29 @@ +ACTIONS_RUNNER_DEBUG= # boolean +ACTIONS_STEP_DEBUG= # boolean +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_S3_BUCKET= +AWS_BUCKET_INTEGRATION= +AWS_BUCKET_STAGING= + +DOCKER_HUB_USERNAME= +DOCKER_HUB_ACCESS_TOKEN= + +GIT_RELEASE_TARGET_REPO= # repo to draft a release under, in / format +GIT_REPO_ACCESS_TOKEN= # only needed when pushing to a repo other than 'self' + +GIT_DEPLOY_HOMEBREW_TAP= + +SIGNING_KEY_GPG= +SIGNING_KEY_GPG_ID= +SIGNING_KEY_GPG_PASSPHRASE= + +SIGNING_CRT_MAC= +SIGNING_KEY_MAC= +SIGNING_KEY_MAC_ID= +SIGNING_KEY_MAC_PASSPHRASE= + +SIGNING_KEY_WINDOWS_PFX= +SIGNING_KEY_WINDOWS_ID= +SIGNING_KEY_WINDOWS_PASSPHRASE= + diff --git a/.github/win/cf.ico b/.github/win/cf.ico new file mode 100644 index 0000000000000000000000000000000000000000..4381126babf6c89e87ec603553f9a2404aa45960 GIT binary patch literal 13094 zcmeHN2UJwo*8c9D85jYnq5)|Z#6pY>MFj;^K&+?~1wn#?NLN&fh?GI87Q`}ev7uO> zMq{F}8;!;mHP)xtp1mX*O?jrhf1f*)K?Y3LTW|eut(Pt5&MEubr`&z^+521p4pJB! zgMQ_RlmVUq?CjL#jdEZc)phGuT{Z;#DY?0+%N<(+zgqzv2}2@4Fi{TuuuL7XJSC@o z&gj#>ANsiTL%)HpaP@G*01tP#4H<+%UY;1_?FsMU!{9w)7<@*Jz;NGD7&*olBggt; z6s56&0T{>9_(1qg2!wyocmxIqA#h?4CWKBz_>>6DNJ_%YWJ)Q?NKQ+mGzYWO=O8U3 z9dk0XFgGg`8)VV_wcQX8j!(dolYz+0QBummy!-;>73L$qr~pNa7GlwoVw5afiWOz0C@mMT zzbubZK31J9 zxwQ~SE0<6z!_f_8IJJE_r4_iiXFe|PRpRWCefaA01Gsi{AFdtSho{#BJi5FV53g*- z{YxL==&_?Xe&RUJoIQiHUwwtkS1wb!f@@dKQ@Vib*RSJ7RTXaDx`o?!?%@8N3wUt% zJf1$NqI45a9^b~B7pL*_%hR}b?;ajLdWffw@8IdPr+D`KIi7#R(l_|_+i&sW#S6TA z`4Zo~dWBc7U*pFgf5e+NZ}9WaKjW8Qe!;K5{)*pz`whSU{yYBluYc9)#$UPrnH9*& z%1R2EJk&rkFkUf>3Pe7-dM)02&(x^d5V4qG!#R%|Im)juY!g{UQh(`S|F&%dJazib z5E)Y4d%F#A9n{BTwpgUc-034m%*?1MWo1r^@EtuREVH^I)2n&#@Zl2;y{aoh<*lO~ zI!0N@Lq&#+Hu~XX+O;41p?(`tM2fjpPzQU*_a|7Jr!a$(ls*ny=S;=U z`2)!YyI}8v-uP@mKO9=(hy%sFur*@{KFS%4U3qT!WPvZ);pX^4)fy*-7G#S%;K;Jh zIJTlKzFMP)uh;Rox{5cw4zttPZ*T>@2 z<`~>KsK7rCx#QvI?znq22w7yub8_cnUS2K=78Hp#d=|B=&3*m+0bYOe3_rbkgrB~9 zjNg8Kf$zWn9zXr_pOk;~m%Y{4BL53nr6y&KkjUf_nl&}~RR0Mv@e$TC@GuNWPEC$X zu4xf}XIp^R)#a-fteJi@iE(^AhSA`8=o(v$7}459msup!dIFNIz)4z9ojoTdWlmMLCM&c+>H93^CSnp7&RV+1c3H*w}?B19(oiMZI}U zkijS>D>1~zHY8CQV+whYrhKI`xrJONw~mXpwT+G&CYR~f>z;L#86V@XH!Q)feZnx4 zfT)<%y3Xrz$;s2(x1XM@wN1^H*uFzz9sk?8Oy$fD9cC))kF1s{*>{9;rh~&w<*3p1 z)~=c%(ip9k(;Xb9DTl!%vKCd7iD(8sKpEDy?T5-h9?j}44)Me{=g?xda^eRcOjM?t z0?q5cK9b4lO;t|oLFd^FWuzX5VKsuq-xys^5}-^Q(z$!*&O_3b0a8vkMw4MI2lLFi zo}D{8cIn*3GdErC|+{+_7 zLIP8bWy&;xWJpgXsJ{;>2wG~$SB?a%l}Q8oi{zF> z)5S>5cIrE9&Wok=WOpW0YXON6e_Ubi_ zZ^d!CL7H&Nk0lM)8x;{37c@$rw0rDZ&C0AXZ12$~lh7uUjj1nPWFBuK?mwRAO(wjJ z7R#yrEt|=t@|OOZ3i|(TtOHKG-zJ2bD8IuqjP}4KpT@f8t91 ziBq+ILUCeoPwZRdjBVMT*qZ5$?eqMwW8PqVoacd$=KEp)@&Ig8#$s1~GIr!A;?u>E zI8+{wPZp(M&(b;AFU-d2mCbRYtR;?@TH$258TmB!II^@0`7~{Cyu2IEl3#Oftu8LE z=W%|W0r@u&{hO+7&2V+IKKVD!xW20^?(CG~9|!v4@Cr8^DIbp`E2rR#HGw#{GYJrNNNEv@mzH1|`8h03U9MUo`Z=p8PTgOY zfkUMQSi5c=DmQMxhD{r>Y0GA8-|-PX-n9$N|Jk>HzdASV^odTNIW77` z7cPFS_JyupL)FbHbzFLxe4(os$mjVQ&+lEs%ZF$1^3gSX_xK7PJbZvhj~`RpU5ZJc zkstH~KfV4IZ+>`)U*9|z{h&8Lyv8qYe)`J~s@Z@4CBK3;%4jB&$@R0fHEFBMG3I!l z<60JLYpkow4YiCSS3f>TPV7XSL3D0ONpW^`ZXJ(CE-M%xbYbvuxsnqbElwn3mLXX=H-PccM}7dgj5AQ==k-Tj^0(Y$oy?4X)a(%1q=p zi>;kzaePY&6)y?Sq1t++GN3V&sP|Mt z=`;r`Mz#*qN(Dh2B&S35^(oRw%3Qy2K`5}bvaw=+R&5FdA(G92*9oo9O(j)=3Gthi z2&L0|+uPb$S=;xXt`bzOh}|$U_igbEo}){WEb|1RG~eFV+CINb5Q~!qN^_RvnfK$}oL8dtj_(dBovm2M1Z2$epy(qM3>}ranT6 z8QV-oG{?mDk+@@+tTS>QnisvnC;%hooEozK%!JZWkd~T z4fs{W&zY!%)Ed?S0|vOcxh4xL6Wa3)`Gr0DGp{u~`rtyXlk zG=8|6*T-krtTHl)w9YzObXuISlwM3(`siUkM56-INou^5#M72&NHMj_zG7}lU|`DJ z6~vQk$n+_cV^RmZ7ad-VXjQ}0E0>g*dPi>#Hxdbdclj`sgE2^ck1eImSY3_C{Fc< z|Ag@vKQWl@)I-HPbr!EGrmFAMqv%dOb{gHOPoIYAGiQk3EoY@BBPDG%(z7y9t{92( zD2i97P`nyF8mnV`Q9<|WYiEw4cy+iKudYoTjdjz5v2oUi*q9tH#;oi<{j;Jzbf^A- zc&A<$vks-0)t6${K@_vP#QhFl~l*59Ik9J!Pnac;Ldj7?oM6YJJ1tHR72<5RcU6Df`!WXKTuj2fQ;BphpN;wXbSF>o zsxo(;crU-WWQlk$zl>tlvX$j1r+fKTYgW^@TO|$(g;+~>^y^u?O5asCZ`~rstL&R9 zyQ|-KV80l*9z1+Vj9X7^&ZqCItMJ8!6*%|lLP|?Nj$E)iYGU(;hTH6@#^6bynJ#CZ(iTVeY)3w^!Sk&$Fi9A z#mg6X_3Sa;{P-L{|44WI;+_6a_~-X8DYpIouXwg*pZyWht@f@ZGo}bjj z2nytNSUQWPB8i81jY!_0pb30Mi)uW5P%xRONXX02&r67!XhxqKY6M3&0k0H{MK+#q z9$vVrjm9_)vf{+g;C-vac0zBF{@pT02`ZjvuF(} zEJWqdZ535S!n9MVH5-6*3TrJ%i|p9erj2d8$g~nxj$_+FYT5K=ERr(;I$`4aS46gL zYikq9z?FhRm*AZFO0m@FBVNj}6_gk$SU*$k?Aq8&rOd);Q}O+aQ)?7V4W)|ay1KMY zI9?vIvNFlW&ekTWa^)n7E!fME8bxV+b(#`O3kwSpLYq@}36*KKZ5(XVD&r)a%rqo6 zZ$@6NanM9)%a>Y6B~g`gY}>SPNUNN}$>ZMH?^{xYg7}4%b8Q{k*v_q7G`;CXe+yby z%xXiFRagGAxeN!Qe+%En!C}dt?X`}7i9>q_hm5-NpU73P1@6_Vx@P1=)LAH$AmApp}zptME}&pG9yHA5W2#ZulP? zLa7DYL9#r;r`y|iuurEXR~}1tEQV4v4K@+r6P4Vl8LN!nRE9Z-Fx)v?!1Bu6;@1ugg0n% z2}0GQG%siPE{8!qSW;70clR8sZY55Dsisz%NM{7kv5o83 zmB7_l4-a>D_d#xcR2|9=5nfadZ33*=0m~^WD=U1vdU()H8zuU4b)`SoF*E@M!*f>J zu&mCKjh}7&b)yFmnve`gG|<(x|1iQwF%94>8^g2yC9MDT;|5eCUD;({|NfpXE@NpM zio3A!{wIm}NhIFi)z#CLecbnCmwAH+5BBm3rfM}qNMrpgL_E`vBY!rIV}rQ zAfAtC44)?|sGKSI1TX4RByTTo@1fq_6R5IC)Gz6b#_($W5EXpQSTDj6(RCrCz%>LC zw}+@-jYwb~l7urZqh{6kpN3(NJ0*0G*U@x zUs*X>w1MLGXhcM{`zvR*yRve1Zct!AKwwZV8+&DCp6H#b4YOFGcj2T?Qzr%tT$kl9m^&PE>D79J@>nh%Hc-2TrP&>QzWYGUoNvvsq=Wpp`{knjQ#L!Ii z!^y1vaVk=n>e5zat4>c{Ger}pj&`X+{$z6_JzZV7p}8Wj_Ek3E{SRL+{3ZUsd ExpandConstant('{userappdata}\Cloud Foundry')) AND + (WizardDirValue <> ExpandConstant('{pf}\Cloud Foundry')) then + begin + Log('User has selected a custom install path. Will not override.') + end + else if OptionPage.Values[1] then + begin + // override the default installation to program files ({pf}) + WizardForm.DirEdit.Text := ExpandConstant('{userappdata}\Cloud Foundry') + end + else + begin + WizardForm.DirEdit.Text := ExpandConstant('{pf}\Cloud Foundry'); + end; + end; + Result := True; +end; diff --git a/.github/win/innosetup/Dockerfile b/.github/win/innosetup/Dockerfile new file mode 100644 index 00000000000..0a0d5a18ed3 --- /dev/null +++ b/.github/win/innosetup/Dockerfile @@ -0,0 +1,19 @@ +# This image is not being used by the github actions workflow +# because gh-actions doesn't support windows based images +# Keeping this file as we expect to use it in the future +FROM mcr.microsoft.com/windows/servercore:ltsc2019 +SHELL ["powershell.exe"] + +ARG CHOCO_THUMBPRINT=83AC7D88C66CB8680BCE802E0F0F5C179722764B +RUN mkdir \setup + +RUN (New-Object System.Net.WebClient).DownloadFile('https://chocolatey.org/install.ps1', '\setup\installChocolatey.ps1') +RUN (Get-AuthenticodeSignature \setup\installChocolatey.ps1).SignerCertificate.Thumbprint > \setup\thumbprint +RUN if ((type \setup\thumbprint) -ne $env:CHOCO_THUMBPRINT) { \ + throw 'chocolatey installer thumbprint does not match expected. see https://docs.chocolatey.org/en-us/information/security' \ + } +RUN \setup\installChocolatey.ps1 +RUN Remove-Item -Recurse \setup + + +RUN choco install --no-progress -r -y innosetup diff --git a/.github/win/install-innosetup.ps1 b/.github/win/install-innosetup.ps1 new file mode 100644 index 00000000000..14c64bf0908 --- /dev/null +++ b/.github/win/install-innosetup.ps1 @@ -0,0 +1,18 @@ +$ErrorActionPreference = "Stop" +# in the future, this variable should cause PS to exit on non-zero exit codes from commands/exes (as opposed to PS cmdlets) +$PSNativeCommandUseErrorActionPreference = $true +# see https://github.com/PowerShell/PowerShell/issues/3415 and https://github.com/PowerShell/PowerShell-RFC/pull/277 + +# retrieved from https://docs.chocolatey.org/en-us/information/security +$chocoThumbprint = '83AC7D88C66CB8680BCE802E0F0F5C179722764B' + +$scriptPath = (Get-Location).Path + '\installChocolatey.ps1' +(New-Object System.Net.WebClient).DownloadFile('https://chocolatey.org/install.ps1', $scriptPath) +(Get-AuthenticodeSignature .\installChocolatey.ps1).SignerCertificate.Thumbprint -eq $chocoThumbprint + +Set-ExecutionPolicy Bypass -Scope Process +.\installChocolatey.ps1 + +choco install --no-progress -r -y innosetup --force + +Get-Command iscc -ErrorAction Continue diff --git a/.github/win/run-innosetup.ps1 b/.github/win/run-innosetup.ps1 new file mode 100644 index 00000000000..b1f1143550a --- /dev/null +++ b/.github/win/run-innosetup.ps1 @@ -0,0 +1,20 @@ +param ($InnoSetupConfig, $CfBinary, $InstallerOutput) + +$ErrorActionPreference = "Stop" +# in the future, this variable should cause PS to exit on non-zero exit codes from commands/exes (as opposed to PS cmdlets) +$PSNativeCommandUseErrorActionPreference = $true +# see https://github.com/PowerShell/PowerShell/issues/3415 and https://github.com/PowerShell/PowerShell-RFC/pull/277 + +$innoSetupWorkDir = "$PSScriptRoot" +$licenseDir = "${PSScriptRoot}\..\license" + +Move-Item -Force "$CfBinary" $innoSetupWorkDir\cf8.exe + +# convert line-endings +Get-Content ${licenseDir}\LICENSE-WITH-3RD-PARTY-LICENSES | Set-Content "${innoSetupWorkDir}\LICENSE" +Get-Content ${licenseDir}\CF_NOTICE | Set-Content "${innoSetupWorkDir}\NOTICE" + +iscc "$InnoSetupConfig" +Move-Item "${innoSetupWorkDir}\Output\mysetup.exe" "$InstallerOutput" + +Get-ChildItem "${innoSetupWorkDir}\Output" diff --git a/.github/win/sign-windows-binary.ps1 b/.github/win/sign-windows-binary.ps1 new file mode 100644 index 00000000000..684b2ab1562 --- /dev/null +++ b/.github/win/sign-windows-binary.ps1 @@ -0,0 +1,9 @@ +# expected environment variables +# SIGNING_KEY_WINDOWS_PASSPHRASE + +param ($BinaryFilePath) + +# add PATH to signtool.exe +$env:PATH="$env:PATH;C:\Program Files (x86)\Windows Kits\10\bin\x64" + +signtool sign /v /p "$env:SIGNING_KEY_WINDOWS_PASSPHRASE" /fd SHA256 /f "$env:RUNNER_TEMP\cert.pfx" "$BinaryFilePath" diff --git a/.github/win/windows-installer-v8-x64.iss b/.github/win/windows-installer-v8-x64.iss new file mode 100644 index 00000000000..c11acb04151 --- /dev/null +++ b/.github/win/windows-installer-v8-x64.iss @@ -0,0 +1,51 @@ +[Setup] +ChangesEnvironment=yes +AlwaysShowDirOnReadyPage=yes +AppName=Cloud Foundry CLI +AppVersion=VERSION +AppVerName=Cloud Foundry CLI version VERSION +AppPublisher=Cloud Foundry Foundation +ArchitecturesInstallIn64BitMode=x64 ia64 +ArchitecturesAllowed=x64 ia64 +PrivilegesRequired=none +DefaultDirName={pf}\Cloud Foundry +SetupIconFile=cf.ico +UninstallDisplayIcon={app}\cf.ico + +[Registry] +Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; Check: IsAdminLoggedOn and Uninstall32Bit() and NeedsAddPath(ExpandConstant('{app}')) +Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; Check: not IsAdminLoggedOn and Uninstall32Bit() and NeedsAddPath(ExpandConstant('{app}')) + +[Files] +Source: LICENSE; DestDir: "{app}" +Source: NOTICE; DestDir: "{app}" +Source: cf8.exe; DestDir: "{app}" +Source: cf.ico; DestDir: "{app}" + +[Run] +Filename: "{cmd}"; Parameters: "/C mklink ""{app}\cf.exe"" ""{app}\cf8.exe""" + +[UninstallDelete] +Type: files; Name: "{app}\cf.exe" +Type: dirifempty; Name: "{app}" + +[Code] +function Uninstall32Bit(): Boolean; +var + resultCode: Integer; + uninstallString: String; + uninstallStringPath: String; +begin + uninstallString := ''; + uninstallStringPath := 'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Cloud Foundry CLI_is1'; + RegQueryStringValue(HKLM, uninstallStringPath, 'UninstallString', uninstallString); + + if uninstallString <> '' then + begin + uninstallString := RemoveQuotes(uninstallString); + Exec(uninstallString, '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART','', SW_HIDE, ewWaitUntilTerminated, resultCode) + end; + Result := true; +end; + +#include "common.iss" diff --git a/.github/win/windows-installer-v8-x86.iss b/.github/win/windows-installer-v8-x86.iss new file mode 100644 index 00000000000..aa3343d012b --- /dev/null +++ b/.github/win/windows-installer-v8-x86.iss @@ -0,0 +1,31 @@ +[Setup] +ChangesEnvironment=yes +AlwaysShowDirOnReadyPage=yes +AppName=Cloud Foundry CLI +AppVersion=VERSION +AppVerName=Cloud Foundry CLI version VERSION +AppPublisher=Cloud Foundry Foundation +PrivilegesRequired=none +DefaultDirName={pf}\Cloud Foundry +SetupIconFile=cf.ico +UninstallDisplayIcon={app}\cf.ico + +[Registry] +Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; Check: IsAdminLoggedOn and NeedsAddPath(ExpandConstant('{app}')) +Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; Check: not IsAdminLoggedOn and NeedsAddPath(ExpandConstant('{app}')) + +[Files] +Source: LICENSE; DestDir: "{app}" +Source: NOTICE; DestDir: "{app}" +Source: cf8.exe; DestDir: "{app}" +Source: cf.ico; DestDir: "{app}" + +[Run] +Filename: "{cmd}"; Parameters: "/C mklink ""{app}\cf.exe"" ""{app}\cf8.exe""" + +[UninstallDelete] +Type: files; Name: "{app}\cf.exe" +Type: dirifempty; Name: "{app}" + +[Code] +#include "common.iss" diff --git a/.github/workflows/build-sign-upload.yml b/.github/workflows/build-sign-upload.yml new file mode 100644 index 00000000000..37bbdeaccdf --- /dev/null +++ b/.github/workflows/build-sign-upload.yml @@ -0,0 +1,880 @@ + +name: Build-Sign-Upload + +on: + push: + tags: + - 'v8.*' + +permissions: + contents: read + +defaults: + # top-level defaults subkeys apply to jobs + # run subkeys apply to all steps within all jobs + run: + shell: bash + +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + if: ${{ github.action_repository != 'cloudfoundry/cli' }} + outputs: + secrets-environment: ${{ steps.set-secrets-environment.outputs.secrets-environment }} + build-version: ${{ steps.set-build-version.outputs.build-version }} + go-version: ${{ steps.set-go-version.outputs.go-version }} + steps: + # note the key must be 'id' and not 'name' here, to be accessed via the steps. syntax above + + - name: Set environment + id: set-secrets-environment + run: echo "::set-output name=secrets-environment::PROD" + + - name: Checkout cli + uses: actions/checkout@v2 + + - name: Check if BUILD_VERSION matches tag ${{ github.ref }} + run: | + echo "Git Ref: ${{ github.ref }}" + echo "BUILD_VERSION: $(cat BUILD_VERSION)" + + exit 0 + + - name: Set build version + id: set-build-version + run: | + version=$(cat BUILD_VERSION) + echo "::set-output name=build-version::$version" + + - name: Set go version + id: set-go-version + run: echo "::set-output name=go-version::1.17" + + # This is for debugging. It's equivalent to fly intecept + # - name: Setup upterm session + # env: + # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + # if: always() + # uses: lhotari/action-upterm@v1 + # timeout-minutes: 60 + + test-rpm-package: + name: Test RPM Artifacts + needs: build-linux + runs-on: ubuntu-latest + container: + image: fedora + steps: + + - name: Download Signed Linux Packages + uses: actions/download-artifact@v2 + with: + name: cf-cli-linux-rpm-packages + + - name: Display structure of downloaded files + run: ls -R + + - name: Test RPMs + run: | + rpm -q --qf 'FN:\t%{FILENAMES}\nNAME:\t%{NAME}\nPGP:\t%{SIGPGP:pgpsig}\nGPG:\t%{SIGGPG:pgpsig}\n' -p *.rpm + + test-deb-package: + name: Test Debian Artifacts + needs: build-linux + runs-on: ubuntu-latest + container: + image: ubuntu + steps: + + - name: Download Signed Linux Packages + uses: actions/download-artifact@v2 + with: + name: cf-cli-linux-deb-packages + + - name: Display structure of downloaded files + run: | + ls -R + ls *.deb | xargs -n1 dpkg --info + + test-macos: + name: Test macOS Artifacts + needs: build-macos + runs-on: macos-latest + steps: + + - name: Download Signed macOS Packages + uses: actions/download-artifact@v2 + with: + name: cf-cli-macos-packages + + - name: Inspect macOS packages + run: | + ls -R + pkgutil --check-signature * + + test-windows: + name: Test Windows Artifacts + needs: build-windows + runs-on: windows-latest + defaults: + run: + shell: pwsh + steps: + + - name: Download Signed Windows Binaries + uses: actions/download-artifact@v2 + with: + name: cf-cli-windows-binaries + + - name: Inspect Windows packages + run: | + Get-AuthenticodeSignature -Verbose -ErrorAction Stop .\cf-cli_win32.exe + Get-AuthenticodeSignature -Verbose -ErrorAction Stop .\cf-cli_winx64.exe + + - name: Download Signed Windows Binaries + uses: actions/download-artifact@v2 + with: + name: cf-cli-windows-packages + + - name: Inspect Windows files + run: | + Get-ChildItem -Force + + - name: View installer signature + run: | + Expand-Archive -DestinationPath winx64 -Path cf8-cli-installer_*_winx64.zip + Expand-Archive -DestinationPath win32 -Path cf8-cli-installer_*_win32.zip + + Get-AuthenticodeSignature -Verbose -ErrorAction Stop ".\winx64\cf8_installer.exe" + Get-AuthenticodeSignature -Verbose -ErrorAction Stop ".\win32\cf8_installer.exe" + + build-linux: + name: Build Linux + needs: + - setup + runs-on: ubuntu-latest + environment: ${{ needs.setup.outputs.secrets-environment }} + + env: + BUILD_VERSION: ${{ needs.setup.outputs.build-version }} + + steps: + + - name: Get Build Version + id: get_build_version + run: echo "BUILD_VERSION $BUILD_VERSION" + + - name: Checkout cli + uses: actions/checkout@v2 + + - name: Checkout cli-ci + uses: actions/checkout@v2 + with: + repository: cloudfoundry/cli-ci.git + path: cli-ci + ref: master + + - name: Install Linux Packages + run: sudo apt update && sudo apt install -y --no-install-recommends fakeroot + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ needs.setup.outputs.go-version }} + + - name: Print go environment + id: go-cache-paths + run: | + echo "::set-output name=go-build::$(go env GOCACHE)" + echo "::set-output name=go-mod::$(go env GOMODCACHE)" + go env + + - name: Go Assets Cache + uses: actions/cache@v2 + with: + path: | + ${{ steps.go-cache-paths.outputs.go-mod }} + ${{ steps.go-cache-paths.outputs.go-build }} + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Build Linux + run: | + make out/cf-cli_linux_i686 + make out/cf-cli_linux_x86-64 + + - name: Store Linux Binaries + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: cf-cli-linux-binaries + path: out/cf-cli_linux* + + - name: Build RedHat Packages + env: + SIGNING_KEY_GPG_ID: ${{ secrets.SIGNING_KEY_GPG_ID }} + run: | + set -ex + set -o pipefail + + root=$PWD + + cat<< EOF >~/.rpmmacros + $SIGNING_KEY_GPG_ID + EOF + + RPM_VERSION=${BUILD_VERSION//-/_} + + mkdir -pv $root/packaged + + echo "Build 32-bit RedHat package" + ( + pushd cli-ci/ci/installers/rpm + cp $root/out/cf-cli_linux_i686 cf8 + cp ../../license/NOTICE . + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES LICENSE + cp ../completion/cf8 cf8.bash + echo "Version: ${RPM_VERSION}" > cf-cli.spec + cat cf8-cli.spec.template >> cf-cli.spec + rpmbuild --target i386 --define "_topdir $(pwd)/build" -bb cf-cli.spec + mv build/RPMS/i386/cf8-cli*.rpm $root/packaged/cf8-cli-installer_${BUILD_VERSION}_i686.rpm + popd + ) + + + echo "Build 64-bit RedHat package" + ( + pushd cli-ci/ci/installers/rpm + cp $root/out/cf-cli_linux_x86-64 cf8 + cp ../../license/NOTICE . + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES LICENSE + cp ../completion/cf8 cf8.bash + echo "Version: ${RPM_VERSION}" > cf-cli.spec + cat cf8-cli.spec.template >> cf-cli.spec + rpmbuild --target x86_64 --define "_topdir $(pwd)/build" -bb cf-cli.spec + mv build/RPMS/x86_64/cf8-cli*.rpm $root/packaged/cf8-cli-installer_${BUILD_VERSION}_x86-64.rpm + popd + ) + + - name: Load GPG key + env: + SIGNING_KEY_GPG: ${{ secrets.SIGNING_KEY_GPG }} + run: | + echo -n "$SIGNING_KEY_GPG" | base64 --decode | gpg --no-tty --batch --pinentry-mode loopback --import + + - name: View GPG keys + run: | + gpg --list-keys + + - name: Sign RedHat Packages + env: + SIGNING_KEY_GPG_ID: ${{ secrets.SIGNING_KEY_GPG_ID }} + SIGNING_KEY_GPG_PASSPHRASE: ${{ secrets.SIGNING_KEY_GPG_PASSPHRASE }} + run: | + set -ex + set -o pipefail + + SUFFIX=8 + + mkdir signed-redhat-installer + + cat<< EOF >~/.rpmmacros + %_gpg_name $SIGNING_KEY_GPG_ID + EOF + + cp packaged/cf*.rpm signed-redhat-installer/ + + # TODO: consider to add --key-id + rpmsign --addsign signed-redhat-installer/*.rpm + + - name: Print RPM Signature + run: rpm -q --qf 'FN:\t%{FILENAMES}\nNAME:\t%{NAME}\nPGP:\t%{SIGPGP:pgpsig}\nGPG:\t%{SIGGPG:pgpsig}\n' -p *.rpm + working-directory: signed-redhat-installer + + - name: Store Signed Linux RPM Packages + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: cf-cli-linux-rpm-packages + path: signed-redhat-installer/*.rpm + + + - name: Build Debian Packages + run: | + set -ex + set -o pipefail + + root=$PWD + + mkdir -pv $root/packaged-deb + + echo "Build 32-bit Debian package" + ( + SIZE="$(BLOCKSIZE=1000 du $root/out/cf-cli_linux_i686 | cut -f 1)" + + pushd cli-ci/ci/installers/deb + mkdir -p cf/usr/bin cf/usr/share/doc/cf8-cli/ cf/DEBIAN cf/usr/share/bash-completion/completions + + cp copyright_preamble cf/DEBIAN/copyright + sed 's/^$/ ./' $root/LICENSE >> cf/DEBIAN/copyright + cat copyright_comment_header >> cf/DEBIAN/copyright + sed 's/^$/ ./' ../../license/3RD-PARTY-LICENSES >> cf/DEBIAN/copyright + + cp cf/DEBIAN/copyright cf/usr/share/doc/cf8-cli/copyright + + cp ../../license/NOTICE cf/usr/share/doc/cf8-cli + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES cf/usr/share/doc/cf8-cli/LICENSE + + cp control_v8.template cf/DEBIAN/control + echo "Installed-Size: ${SIZE}" >> cf/DEBIAN/control + echo "Version: ${BUILD_VERSION}" >> cf/DEBIAN/control + echo "Architecture: i386" >> cf/DEBIAN/control + + cp ../completion/cf8 cf/usr/share/bash-completion/completions/cf8 + + cp $root/out/cf-cli_linux_i686 cf/usr/bin/cf8 + ln -frs cf/usr/bin/cf8 cf/usr/bin/cf + + fakeroot dpkg --build cf cf8-cli-installer_${BUILD_VERSION}_i686.deb + mv cf8-cli-installer_${BUILD_VERSION}_i686.deb $root/packaged-deb + rm -rf cf + popd + ) + + echo "Build 64-bit Debian package" + ( + SIZE="$(BLOCKSIZE=1000 du $root/out/cf-cli_linux_x86-64 | cut -f 1)" + + pushd cli-ci/ci/installers/deb + mkdir -p cf/usr/bin cf/usr/share/doc/cf8-cli/ cf/DEBIAN cf/usr/share/bash-completion/completions + + cp copyright_preamble cf/DEBIAN/copyright + sed 's/^$/ ./' $root/LICENSE >> cf/DEBIAN/copyright + cat copyright_comment_header >> cf/DEBIAN/copyright + sed 's/^$/ ./' ../../license/3RD-PARTY-LICENSES >> cf/DEBIAN/copyright + + cp cf/DEBIAN/copyright cf/usr/share/doc/cf8-cli/copyright + + cp ../../license/NOTICE cf/usr/share/doc/cf8-cli + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES cf/usr/share/doc/cf8-cli/LICENSE + + cp control_v8.template cf/DEBIAN/control + echo "Installed-Size: ${SIZE}" >> cf/DEBIAN/control + echo "Version: ${BUILD_VERSION}" >> cf/DEBIAN/control + echo "Architecture: amd64" >> cf/DEBIAN/control + + cp ../completion/cf8 cf/usr/share/bash-completion/completions/cf8 + + cp $root/out/cf-cli_linux_x86-64 cf/usr/bin/cf8 + ln -frs cf/usr/bin/cf8 cf/usr/bin/cf + + fakeroot dpkg --build cf cf8-cli-installer_${BUILD_VERSION}_x86-64.deb + mv cf8-cli-installer_${BUILD_VERSION}_x86-64.deb $root/packaged-deb + popd + ) + + - name: Print DEB Packages Info + run: | + ls -R + for f in *.deb; do + echo $f + dpkg --info $f + done + working-directory: packaged-deb + + - name: Store Debian Packages + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: cf-cli-linux-deb-packages + path: packaged-deb/*.deb + + build-macos: + name: Build macOS + needs: + - setup + runs-on: macos-latest + environment: ${{ needs.setup.outputs.secrets-environment }} + steps: + + - name: Setup + run: | + echo "BUILD_VERSION=${{ needs.setup.outputs.build-version }}" >> $GITHUB_ENV + + - name: Get Build Version + id: get_build_version + run: echo "BUILD_VERSION $BUILD_VERSION" + + - name: Checkout cli + uses: actions/checkout@v2 + + - name: Checkout cli-ci + uses: actions/checkout@v2 + with: + repository: cloudfoundry/cli-ci.git + path: cli-ci + ref: master + + - name: Checkout bomutils + uses: actions/checkout@v2 + with: + repository: hogliux/bomutils.git + ref: 0.2 + path: bomutils + + - name: Build bomutils + working-directory: bomutils + run: make + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ needs.setup.outputs.go-version }} + + - name: Update Homebrew + run: | + brew tap pivotalsoftware/gon + brew update --preinstall + cat "$(brew --repository)/Library/Taps/pivotalsoftware/homebrew-gon/gon.rb" > .github/brew-formulae + + - name: Configure Homebrew cache + uses: actions/cache@v2 + with: + path: | + ~/Library/Caches/Homebrew/gon--* + ~/Library/Caches/Homebrew/downloads/*--gon-* + key: brew-${{ hashFiles('.github/brew-formulae') }} + restore-keys: brew- + + - name: Install Homebrew dependencies + run: | + env HOMEBREW_NO_AUTO_UPDATE=1 brew install pivotalsoftware/gon/gon coreutils + + - name: Print go environment + id: go-cache-paths + run: | + echo "::set-output name=go-build::$(go env GOCACHE)" + echo "::set-output name=go-mod::$(go env GOMODCACHE)" + go env + + - name: Go Assets Cache + uses: actions/cache@v2 + with: + path: | + ${{ steps.go-cache-paths.outputs.go-mod }} + ${{ steps.go-cache-paths.outputs.go-build }} + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Build macOS Binaries + run: | + make out/cf-cli_osx + make out/cf-cli_osx_arm + + - name: Store macOS Binaries + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: cf-cli-macos-binaries + path: out/cf-cli* + + - name: Build macOS Installer + run: | + set -ex + set -o pipefail + + root=$PWD + + mkdir -pv $root/packaged + + echo "Building OS X installer" + ( + SIZE="$(BLOCKSIZE=1000 du $root/out/cf-cli_osx | cut -f 1)" + + pushd cli-ci/ci/installers/osx_v8 + sed -i -e "s/VERSION/${BUILD_VERSION}/g" Distribution + sed -i -e "s/SIZE/${SIZE}/g" Distribution + mkdir -p cf-cli/usr/local/bin cf-cli/usr/local/share/doc/cf8-cli + + cp $root/out/cf-cli_osx cf-cli/usr/local/bin/cf8 + gln -frs cf-cli/usr/local/bin/cf8 cf-cli/usr/local/bin/cf + cp ../../license/NOTICE cf-cli/usr/local/share/doc/cf8-cli + cp ../../license/LICENSE-WITH-3RD-PARTY-LICENSES cf-cli/usr/local/share/doc/cf8-cli/LICENSE + chmod -R go-w cf-cli + pushd cf-cli + find usr | cpio -o --format=odc | gzip -c > ../Payload + popd + + $root/bomutils/build/bin/ls4mkbom cf-cli | sed 's/1000\/1000/0\/80/' > bom_list + mkbom -i bom_list Bom + mv Bom Payload com.cloudfoundry.cf8-cli.pkg + xar -c --compression none -f cf8-cli-installer_osx.pkg com.cloudfoundry.cf8-cli.pkg Distribution + mv cf8-cli-installer_osx.pkg $root/packaged/cf8-cli-installer_osx.pkg + popd + ) + + - name: Load macos key + env: + SIGNING_TEST_CA_MAC: ${{ secrets.SIGNING_TEST_CA_MAC }} + SIGNING_KEYCHAIN_PASSPHRASE: ${{ secrets.SIGNING_KEYCHAIN_PASSPHRASE }} + + SIGNING_KEY_MAC: ${{ secrets.SIGNING_KEY_MAC }} + SIGNING_KEY_MAC_ID: ${{ secrets.SIGNING_KEY_MAC_ID }} + SIGNING_KEY_MAC_PASSPHRASE: ${{ secrets.SIGNING_KEY_MAC_PASSPHRASE }} + SIGNING_KEY_MAC_PFX: ${{ secrets.SIGNING_KEY_MAC_PFX }} + + run: | + echo -n "$SIGNING_KEY_MAC_PFX" | base64 --decode > mac-signing-key.p12 + security list-keychains -d user -s login + + ORIGINAL_KEYCHAIN="$(security default-keychain | sed -e 's/[ "]*\([^"]*\)[ "]*/\1/')" + KEYCHAIN_PATH="$HOME/Library/Keychains/build.keychain-db" + + # Create build keychain + security create-keychain -p "$SIGNING_KEYCHAIN_PASSPHRASE" "$KEYCHAIN_PATH" + # trap "security delete-keychain $KEYCHAIN_PATH" 0 + + # Append build keychain to the user domain + security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed s/\"//g) + + # Remove relock timeout + security set-keychain-settings "$KEYCHAIN_PATH" + + # Unlock build keychain + security unlock-keychain -p "$SIGNING_KEYCHAIN_PASSPHRASE" "$KEYCHAIN_PATH" + + # Add certificate to keychain + security import mac-signing-key.p12 -k "$KEYCHAIN_PATH" -P "$SIGNING_KEY_MAC_PASSPHRASE" -A -T $(which codesign) -T $(which productsign) + + # Enable codesigning from a non user interactive shell + security set-key-partition-list -S apple-tool:,apple:, -s -k "$SIGNING_KEYCHAIN_PASSPHRASE" -D "${IDENTITY_CERTIFICATE}" -t private "$KEYCHAIN_PATH" || echo set-key-partition-list private failed + rm mac-signing-key.p12 + + #TODO: clean keychain in a separate step + # Delete build keychain + # security delete-keychain "$KEYCHAIN_PATH" + + - name: Sign macOS + env: + SUFFIX: 8 + SIGNING_KEY_MAC_ID: ${{ secrets.SIGNING_KEY_MAC_ID }} + SIGNING_KEY_MAC_PASSPHRASE: ${{ secrets.SIGNING_KEY_MAC_PASSPHRASE }} + run: | + + root=$PWD + + mkdir -pv signed-osx-installer + + productsign --timestamp \ + --sign "$SIGNING_KEY_MAC_ID" \ + "$root/packaged/cf${SUFFIX}-cli-installer_osx.pkg" \ + "signed-osx-installer/cf${SUFFIX}-cli-installer_${BUILD_VERSION}_osx.pkg" + + - name: Store macOS Signed Packages + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: cf-cli-macos-packages + path: signed-osx-installer/*.pkg + + + build-windows: + name: Build Windows + needs: + - setup + runs-on: windows-2019 + environment: ${{ needs.setup.outputs.secrets-environment }} + defaults: + run: + shell: pwsh + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ needs.setup.outputs.go-version }} + + - name: Get build-time dependencies + run: | + choco install --no-progress --limit-output -y make + go install github.com/akavel/rsrc@v0.10.2 + + - name: Build CF CLI for Windows + run: | + Get-Command make + Get-Item Makefile + make out/cf-cli_win32.exe + make out/cf-cli_winx64.exe + + - name: write windows cert + run: | + $pass = convertto-securestring -string "$env:SIGNING_KEY_WINDOWS_PASSPHRASE" -asplaintext + [convert]::frombase64string($env:SIGNING_KEY_WINDOWS_PFX) | set-content -path $env:runner_temp\cert.pfx -asbytestream + + env: + SIGNING_KEY_WINDOWS_PFX: ${{ secrets.SIGNING_KEY_WINDOWS_PFX }} + SIGNING_KEY_WINDOWS_PASSPHRASE: ${{ secrets.SIGNING_KEY_WINDOWS_PASSPHRASE }} + + - name: Sign windows binaries + run: | + .\.github\win\sign-windows-binary.ps1 -BinaryFilePath out\cf-cli_win32.exe + .\.github\win\sign-windows-binary.ps1 -BinaryFilePath out\cf-cli_winx64.exe + env: + SIGNING_KEY_WINDOWS_PASSPHRASE: ${{ secrets.SIGNING_KEY_WINDOWS_PASSPHRASE }} + + - name: View binary signatures + run: | + Get-AuthenticodeSignature -Verbose -ErrorAction Stop .\out\cf-cli_win32.exe + Get-AuthenticodeSignature -Verbose -ErrorAction Stop .\out\cf-cli_winx64.exe + + - name: Make symlinks + run: | + New-Item -ItemType SymbolicLink -Target .\out\cf-cli_win32.exe -Path .\out\cf-cli_win32-link.exe + New-Item -ItemType SymbolicLink -Target .\out\cf-cli_winx64.exe -Path .\out\cf-cli_winx64-link.exe + + # This is for debugging windows + # - name: enable ssh + # if: always() + # run: | + # Get-WindowsCapability -Online + # $componentName = $(Get-WindowsCapability -Online |Where-Object Name -like 'OpenSSH.Server*').Name + # Add-WindowsCapability -Online -Name $componentName + # Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" + + # - name: ssh session + # if: always() + # run: | + # Start-Service sshd + # echo "IP address below:" + # Get-NetIPAddress | Select-Object -Property IPAddress + # sleep 3600 + # Stop-Service sshd + + - name: Save signed binaries as a GitHub Action Artifact + uses: actions/upload-artifact@v2 + with: + name: cf-cli-windows-binaries + if-no-files-found: error + path: out/cf-cli_win*.exe + + - name: Install innosetup + run: .\.github\win\install-innosetup.ps1 + + - name: Run innosetup + run: | + mkdir "$env:RUNNER_TEMP\winx64" + .\.github\win\run-innosetup.ps1 -InnoSetupConfig ".github\win\windows-installer-v8-x64.iss" -CfBinary "out\cf-cli_winx64.exe" -InstallerOutput "$env:RUNNER_TEMP\winx64\cf8_installer.exe" + mkdir "$env:RUNNER_TEMP\win32" + .\.github\win\run-innosetup.ps1 -InnoSetupConfig ".github\win\windows-installer-v8-x86.iss" -CfBinary "out\cf-cli_win32.exe" -InstallerOutput "$env:RUNNER_TEMP\win32\cf8_installer.exe" + + - name: Sign windows installer + run: | + .\.github\win\sign-windows-binary.ps1 -BinaryFilePath "$env:RUNNER_TEMP\winx64\cf8_installer.exe" + .\.github\win\sign-windows-binary.ps1 -BinaryFilePath "$env:RUNNER_TEMP\win32\cf8_installer.exe" + env: + SIGNING_KEY_WINDOWS_PASSPHRASE: ${{ secrets.SIGNING_KEY_WINDOWS_PASSPHRASE }} + + - name: View installer signature + run: | + Get-AuthenticodeSignature -Verbose -ErrorAction Stop "$env:RUNNER_TEMP\winx64\cf8_installer.exe" + Get-AuthenticodeSignature -Verbose -ErrorAction Stop "$env:RUNNER_TEMP\win32\cf8_installer.exe" + + - name: Arrange files for upload + # note the -Path flag takes comma-delimited args + run: | + Copy-Item -Destination "$env:RUNNER_TEMP\winx64" -Path .github\win\LICENSE,.github\win\NOTICE + Copy-Item -Destination "$env:RUNNER_TEMP\win32" -Path .github\win\LICENSE,.github\win\NOTICE + + - name: Zip windows artifact + run: | + # strip leading v to go from tag -> semver + $installer_release_version="$(cat BUILD_VERSION)".Replace("v", "") + pushd "$env:RUNNER_TEMP\winx64" + $installer_zip_filename="$env:RUNNER_TEMP\cf8-cli-installer_${installer_release_version}_winx64.zip" + Compress-Archive -DestinationPath "$installer_zip_filename" -Path * + popd + pushd "$env:RUNNER_TEMP\win32" + $installer_zip_filename="$env:RUNNER_TEMP\cf8-cli-installer_${installer_release_version}_win32.zip" + Compress-Archive -DestinationPath "$installer_zip_filename" -Path * + popd + Get-ChildItem "$env:RUNNER_TEMP" + + - name: Save installer and dist files as a GitHub Action Artifact + uses: actions/upload-artifact@v2 + with: + name: cf-cli-windows-packages + if-no-files-found: error + path: ${{ runner.temp }}/cf8-cli-installer*win*.zip + +################################# +######## Release Section ######## +################################# + + s3-upload: + name: Upload Artifacts to S3 bucket + if: ${{ github.ref_type == 'tag' }} + runs-on: ubuntu-latest + needs: + - test-rpm-package + - test-deb-package + - test-macos + - test-windows + environment: PROD + permissions: + actions: read + contents: read + steps: + - name: Checkout cli + uses: actions/checkout@v2 + - name: get semver version + # set environment var for subsequent steps. see: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable + run: | + INSTALLER_RELEASE_VERSION=$(cat BUILD_VERSION | tr -d v) + echo "INSTALLER_RELEASE_VERSION=${INSTALLER_RELEASE_VERSION}" >> $GITHUB_ENV + + - name: Download signed artifacts + uses: actions/download-artifact@v2 + with: + path: signed # download all artifacts to 'signed/' + + - name: Archive linux binaries for upload + run: | + + prepare_legal() { + cp ../../.github/license/LICENSE-WITH-3RD-PARTY-LICENSES LICENSE + cp ../../.github/license/CF_NOTICE NOTICE + } + + prepare_artifacts() { + chmod +x cf8 + ln -s cf8 cf + prepare_legal + } + + prepare_win_artifacts() { + cp cf8.exe cf.exe + prepare_legal + } + + pushd signed + mkdir linux_i686 linux_x86-64 + mv cf-cli-linux-binaries/cf-cli_linux_i686 linux_i686/cf8 + mv cf-cli-linux-binaries/cf-cli_linux_x86-64 linux_x86-64/cf8 + pushd linux_i686 + prepare_artifacts + tar -cvzf cf8-cli_${INSTALLER_RELEASE_VERSION}_linux_i686.tgz * + popd + pushd linux_x86-64 + prepare_artifacts + tar -cvzf cf8-cli_${INSTALLER_RELEASE_VERSION}_linux_x86-64.tgz * + popd + + mkdir osx + mv cf-cli-macos-binaries/cf-cli_osx osx/cf8 + pushd osx + prepare_artifacts + tar -cvzf cf8-cli_${INSTALLER_RELEASE_VERSION}_osx.tgz * + popd + + mkdir win32 winx64 + mv cf-cli-windows-binaries/cf-cli_win32.exe win32/cf8.exe + mv cf-cli-windows-binaries/cf-cli_winx64.exe winx64/cf8.exe + pushd win32 + prepare_win_artifacts + # -y flag avoids the default behavior of derefencing the link, so we archive the symlink as-is + zip -y cf8-cli_${INSTALLER_RELEASE_VERSION}_win32.zip * + popd + pushd winx64 + prepare_win_artifacts + # -y flag avoids the default behavior of derefencing the link, so we archive the symlink as-is + zip -y cf8-cli_${INSTALLER_RELEASE_VERSION}_winx64.zip * + popd + popd + env: + INSTALLER_RELEASE_VERSION: ${{ env.INSTALLER_RELEASE_VERSION }} + + - name: Rearrange artifacts before upload + run: | + mkdir upload + cp -t upload \ + signed/cf-cli-linux-rpm-packages/cf*rpm \ + signed/cf-cli-linux-deb-packages/cf*deb \ + signed/cf-cli-macos-packages/cf*pkg \ + signed/cf-cli-windows-packages/cf*zip \ + signed/linux_i686/*tgz \ + signed/linux_x86-64/*tgz \ + signed/osx/*tgz \ + signed/win32/*zip \ + signed/winx64/*zip + + - name: Upload installers to CLAW S3 bucket + if: ${{ github.ref_type == 'tag' }} + # forked for security considerations + uses: pivotalsoftware/s3-sync-action@v0.5.2 #pinned to no-default-region change + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + SOURCE_DIR: upload + DEST_DIR: "releases/v${{ env.INSTALLER_RELEASE_VERSION }}" + + - name: list S3 for human verification + uses: docker://amazon/aws-cli:latest + with: + args: s3 ls "${{ env.AWS_S3_BUCKET }}/releases/v${{ env.INSTALLER_RELEASE_VERSION }}/" + env: + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + INSTALLER_RELEASE_VERSION: ${{ env.INSTALLER_RELEASE_VERSION }} + + - name: Instruct human to update CLAW + run: | + echo "Please go to https://github.com/cloudfoundry/CLAW/blob/develop/claw-variables.yml and add the following line to the file:" + echo + echo "- ${INSTALLER_RELEASE_VERSION}" + + + + github-release-draft: + name: Create GitHub Release Draft + if: ${{ github.ref_type == 'tag' }} + runs-on: ubuntu-latest + environment: PROD + permissions: + actions: read + contents: write + needs: + - test-rpm-package + - test-deb-package + - test-macos + - test-windows + steps: + - name: Download signed artifacts + uses: actions/download-artifact@v2 + with: + path: signed # download all artifacts to 'signed/' + + - name: Create draft release + uses: pivotalsoftware/action-gh-release@v1 + with: + draft: true + repository: ${{ secrets.GIT_RELEASE_TARGET_REPO }} # repo to draft a release under, in / format + token: ${{ secrets.GIT_REPO_ACCESS_TOKEN }} # only needed when pushing to a repo other than 'self' + fail_on_unmatched_files: true + files: | + signed/cf-cli-linux-binaries/cf-cli* + signed/cf-cli-linux-deb-packages/cf*deb + signed/cf-cli-linux-rpm-packages/cf*rpm + signed/cf-cli-macos-binaries/cf-cli* + signed/cf-cli-macos-packages/cf*pkg + signed/cf-cli-windows-binaries/cf-cli*.exe + signed/cf-cli-windows-packages/*.zip + + +# vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=2 fdm=indent nospell: diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index eb0e0323d1b..e404f2dc45a 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -12,6 +12,7 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + if: ${{ github.action_repository == 'cloudfoundry/cli' }} permissions: actions: read contents: read @@ -33,3 +34,5 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 + +# vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=2 fdm=indent nospell: diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index 5c6446e4f65..381e21aa522 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -13,101 +13,110 @@ defaults: shell: bash jobs: - lint: - name: Lint code + shared-values: + name: Shared Values runs-on: ubuntu-latest + outputs: + go-version: ${{ steps.set-go-version.outputs.go-version }} steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.17 - - - name: Run go fmt - run: go fmt && git diff --exit-code + - id: set-go-version + run: | + echo "::set-output name=go-version::1.17" - units-linux: - name: Units Linux + lint: + name: Lint code runs-on: ubuntu-latest + needs: shared-values steps: - - - name: Checkout + + - name: Checkout uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go + + - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 - - - name: Set up Test - run: | - go get -u github.com/onsi/ginkgo/ginkgo - go get github.com/onsi/gomega/matchers@v1.10.5 - - - name: Run Linux Units - run: make units - env: - ACK_GINKGO_RC: true + go-version: ${{ needs.shared-values.outputs.go-version }} + + - name: Run go fmt + run: go fmt && git diff --exit-code - units-macos: - name: Units OS X - runs-on: macos-latest + units: + name: Units + needs: shared-values + strategy: + matrix: + os: + - ubuntu-latest + - ubuntu-18.04 + - macos-latest + - macos-10.15 + runs-on: ${{ matrix.os }} steps: - - - name: Checkout + + - name: Checkout uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go + + - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 - - - name: Set up Test + go-version: ${{ needs.shared-values.outputs.go-version }} + + - name: Set up Test run: | go get -u github.com/onsi/ginkgo/ginkgo go get github.com/onsi/gomega/matchers@v1.10.5 - - - name: Run MacOS Units + + - name: Run Linux Units run: make units env: ACK_GINKGO_RC: true units-windows: name: Units Windows - runs-on: windows-latest + strategy: + matrix: + os: + - windows-2022 + - windows-2019 + - windows-2016 + runs-on: ${{ matrix.os }} + needs: shared-values + defaults: + run: + shell: pwsh steps: - - - name: Checkout + + - name: Checkout uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go + + - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 - - - name: Set up Test + go-version: ${{ needs.shared-values.outputs.go-version }} + + - name: Set up Test run: | go get -u github.com/onsi/ginkgo/ginkgo go get github.com/onsi/gomega/matchers@v1.10.5 - - - name: Run Windows Units - shell: pwsh + + - name: Get build-time dependencies + run: | + choco install --no-progress --limit-output -y make + + - name: Run Windows Units + env: + ACK_GINKGO_RC: true run: > + # Get-Command make + # Get-Item Makefile + # make units-non-plugin + ginkgo -r -p -randomizeAllSpecs -randomizeSuites -skipPackage integration,cf\ssh,plugin,cf\actors\plugin,cf\commands\plugin,cf\actors\plugin,util\randomword -flakeAttempts=2 - env: - ACK_GINKGO_RC: true + +# vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=2 fdm=indent nospell: diff --git a/.github/workflows/update-repos.yml b/.github/workflows/update-repos.yml new file mode 100644 index 00000000000..c12e52459a3 --- /dev/null +++ b/.github/workflows/update-repos.yml @@ -0,0 +1,416 @@ +name: Update Repositories + +on: + workflow_dispatch: + inputs: + build_version: + description: 'Build Version' + required: true + default: '8.3.0' + type: string + +permissions: + contents: write + +defaults: + run: + shell: bash + +jobs: + shared-values: + name: Setup + runs-on: ubuntu-latest + if: ${{ github.action_repository != 'cloudfoundry/cli' }} + outputs: + build-version: ${{ steps.set-build-version.outputs.build-version }} + secrets-environment: ${{ steps.set-secrets-environment.outputs.secrets-environment }} + steps: + + - name: Set environment + id: set-secrets-environment + run: echo "::set-output name=secrets-environment::PROD" + + - name: Checkout cli + uses: actions/checkout@v2 + + - name: Set build version + id: set-build-version + run: | + version=$(cat BUILD_VERSION) + echo "::set-output name=build-version::$version" + + update-homebrew: + name: Update Homebrew Repository + runs-on: ubuntu-latest + needs: shared-values + environment: ${{ needs.shared-values.outputs.secrets-environment }} + env: + BUILD_VERSION: ${{ needs.shared-values.outputs.build-version }} + steps: + + - name: Checkout cli-ci + uses: actions/checkout@v2 + with: + repository: cloudfoundry/cli-ci.git + ref: master + path: cli-ci + + - name: Checkout homebrew-tap + uses: actions/checkout@v2 + with: + repository: cloudfoundry/homebrew-tap + ref: master + path: homebrew-tap + ssh-key: ${{ secrets.GIT_DEPLOY_HOMEBREW_TAP }} + + - name: Setup + run: | + mkdir cf8-cli-osx-tarball cf8-cli-linux-tarball + + - name: Calculate checksums + run: | + set -x + + curl -L "https://packages.cloudfoundry.org/stable?release=macosx64-binary&version=${BUILD_VERSION}&source=github-rel" \ + > cf8-cli-osx-tarball/cf8-cli_${BUILD_VERSION}_osx.tgz + + # Because CLAW always returns 200 we have to check if we got archive + file cf8-cli-osx-tarball/cf8-cli_${BUILD_VERSION}_osx.tgz | grep -q gzip || exit 1 + + curl -L "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=${BUILD_VERSION}&source=github-rel" \ + > cf8-cli-linux-tarball/cf8-cli_${BUILD_VERSION}_linux64.tgz + + # Because CLAW always returns 200 we have to check if we got archive + file cf8-cli-linux-tarball/cf8-cli_${BUILD_VERSION}_linux64.tgz | grep -q gzip || exit 1 + + pushd cf8-cli-osx-tarball + CLI_OSX_SHA256=$(shasum -a 256 cf8-cli_*_osx.tgz | cut -d ' ' -f 1) + popd + + pushd cf8-cli-linux-tarball + CLI_LINUX_64_SHA256=$(shasum -a 256 cf8-cli_*_linux64.tgz | cut -d ' ' -f 1) + popd + + echo "CLI_OSX_SHA256=${CLI_OSX_SHA256}" >> $GITHUB_ENV + echo "CLI_LINUX_64_SHA256=${CLI_LINUX_64_SHA256}" >> $GITHUB_ENV + + - name: Generate Homebrew formula file + run: | + set -ex + + pushd homebrew-tap + cat < cf-cli@8.rb + require 'formula' + + class CfCliAT8 < Formula + homepage 'https://code.cloudfoundry.org/cli' + version '${BUILD_VERSION}' + + if OS.mac? + url 'https://packages.cloudfoundry.org/homebrew/cf8-${BUILD_VERSION}.tgz' + sha256 '${CLI_OSX_SHA256}' + elsif OS.linux? + url 'https://packages.cloudfoundry.org/stable?release=linux64-binary&version=${BUILD_VERSION}&source=homebrew' + sha256 '${CLI_LINUX_64_SHA256}' + end + + depends_on :arch => :x86_64 + + def install + bin.install 'cf8' + bin.install_symlink 'cf8' => 'cf' + (bash_completion/"cf8-cli").write <<-completion + $(cat ../cli-ci/ci/installers/completion/cf8) + completion + doc.install 'LICENSE' + doc.install 'NOTICE' + end + + test do + system "#{bin}/cf8" + end + end + EOF + + popd + + - name: Commit new homebrew formula + run: | + pushd homebrew-tap + git add cf-cli@8.rb + + if ! [ -z "$(git status --porcelain)"]; then + git config user.name github-actions + git config user.email github-actions@github.com + git commit -m "Release CF CLI $BUILD_VERSION" + else + echo "no new version to commit" + fi + + git push + + echo "::group::cf-cli@8.rb" + cat cf-cli@8.rb + echo "::endgroup::" + + echo "::group::git show" + git show + echo "::endgroup::" + + popd + + test-homebrew: + name: Test Homebrew Repository + runs-on: macos-latest + needs: + - shared-values + - update-homebrew + environment: ${{ needs.shared-values.outputs.secrets-environment }} + env: + BUILD_VERSION: ${{ needs.shared-values.outputs.build-version }} + steps: + + - name: Install CF CLI via Homebrew + run: | + set -evx + + brew install cloudfoundry/tap/cf-cli@8 + installed_cf_version=$(cf8 version) + + cf_location=$(which cf) + + echo $cf_location + echo $installed_cf_version + echo $BUILD_VERSION + + codesign --verify $cf_location || echo --- + + cf -v | grep "$BUILD_VERSION" + + update-deb: + name: Update Debian Repository + runs-on: ubuntu-latest + needs: shared-values + environment: ${{ needs.shared-values.outputs.secrets-environment }} + env: + BUILD_VERSION: ${{ needs.shared-values.outputs.build-version }} + AWS_BUCKET_NAME: cf-cli-debian-repo + AWS_DEFAULT_REGION: us-west-2 + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + steps: + + - name: Setup + run: | + echo "BUILD_VERSION: $BUILD_VERSION" + echo "Environment: $ENVIRONMENT" + + - name: Checkout + uses: actions/checkout@v2 + + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.3 + - run: gem install deb-s3 + #RUN apt install -y ruby1.9.1 createrepo + + - name: Load GPG key + env: + SIGNING_KEY_GPG: ${{ secrets.SIGNING_KEY_GPG }} + SIGNING_KEY_GPG_PASSPHRASE: ${{ secrets.SIGNING_KEY_GPG_PASSPHRASE }} + run: | + echo -n "$SIGNING_KEY_GPG" | base64 --decode | gpg --no-tty --batch --pinentry-mode loopback --import + + - name: View GPG keys + run: gpg --list-keys + + - name: Configure GPG + run: | + echo "Configure GPG" + + # mkdir gpg-dir + # export GNUPGHOME=$PWD/gpg-dir + # chmod 700 $GNUPGHOME + # TODO: restore + # trap "rm -rf $GNUPGHOME" 0 + + cat >> ~/gpg.conf < installers/cf8-cli-installer_${BUILD_VERSION}_i686.deb + curl -L "https://cli.run.pivotal.io/stable?release=debian64&version=${BUILD_VERSION}&source=github-rel" > installers/cf8-cli-installer_${BUILD_VERSION}_x86-64.deb + + - name: Update Debian Repository + env: + DEBIAN_FRONTEND: noninteractive + SIGNING_KEY_GPG_ID: ${{ secrets.SIGNING_KEY_GPG_ID }} + run: | + deb-s3 upload installers/*.deb \ + --preserve-versions \ + --bucket=${AWS_BUCKET_NAME} \ + --sign=${SIGNING_KEY_GPG_ID} + + test-deb: + name: Test Debian Repository + runs-on: ubuntu-latest + needs: + - shared-values + - update-deb + environment: ${{ needs.shared-values.outputs.secrets-environment }} + env: + BUILD_VERSION: ${{ needs.shared-values.outputs.build-version }} + steps: + + - name: Install CF CLI via apt + run: | + set -o pipefail -e + + sudo apt update + sudo apt install -y wget gnupg + + wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add - + echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list + + sudo apt update + sudo apt install -y cf8-cli + + which cf + + set -x + + cf -v + cf8 -v + + cf -v | grep "$BUILD_VERSION" + + update-rpm: + name: Update RPM Repository + runs-on: ubuntu-18.04 + environment: ${{ needs.shared-values.outputs.secrets-environment }} + needs: shared-values + env: + AWS_DEFAULT_REGION: us-east-1 + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + steps: + + - name: Setup + env: + BUILD_VERSION: ${{ github.event.inputs.build_version }} + ENVIRONMENT: ${{ github.event.inputs.environment }} + run: | + echo "BUILD_VERSION: $BUILD_VERSION" + echo "Environment: $ENVIRONMENT" + + # TODO: fix backup + # - name: Download current RPM repodata + # env: + # AWS_DEFAULT_REGION: us-east-1 + # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # uses: docker://amazon/aws-cli:latest + # with: + # args: > + # s3 cp --recursive + # s3://cf-cli-rpm-repo/ + # backup + # TODO: fix https://aws.amazon.com/premiumsupport/knowledge-center/s3-access-denied-listobjects-sync/ + # + # - name: List assets + # run: | + # ls -R + # + # - name: Backup current Linux RPM repodata + # uses: actions/upload-artifact@v2 + # with: + # if-no-files-found: error + # name: cf-cli-linux-rpm-repodata-backup + # path: backup + + - name: Install Linux Packages + run: > + sudo apt update && + sudo apt install -y --no-install-recommends + gnupg createrepo + + - name: Download V8 RPMs + uses: docker://amazon/aws-cli:latest + with: + args: > + s3 sync --exclude "*" --include "releases/*/*installer*.rpm" + s3://v8-cf-cli-releases . + + - name: Download V7 RPMs + uses: docker://amazon/aws-cli:latest + with: + args: > + s3 sync --exclude "*" --include "releases/*/*installer*.rpm" + s3://v7-cf-cli-releases . + + - name: Download V6 RPMs + uses: docker://amazon/aws-cli:latest + with: + args: > + s3 sync --exclude "*" --include "releases/*/*installer*.rpm" + s3://cf-cli-releases . + + - name: Sign repo + run: | + createrepo --checksum=sha . + + - name: List assets + run: | + ls -R + + - name: Store Linux RPM repodata + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: cf-cli-linux-rpm-repodata + path: repodata + + - name: Upload RPM repodata + uses: docker://amazon/aws-cli:latest + with: + args: > + s3 sync --delete + repodata + s3://cf-cli-rpm-repo/repodata + + test-rpm-repo: + name: Test RPM Repository + needs: + - shared-values + - update-rpm + runs-on: ubuntu-latest + container: + image: fedora + environment: ${{ needs.shared-values.outputs.secrets-environment }} + env: + BUILD_VERSION: ${{ needs.shared-values.outputs.build-version }} + steps: + + - name: Configure Custom CF Repository + run: | + curl -sL -o /etc/yum.repos.d/cloudfoundry-cli.repo \ + https://packages.cloudfoundry.org/fedora/cloudfoundry-cli.repo + + - name: Install cf8-cli package + run: dnf install -y cf8-cli + + - name: Print CF CLI Versions + run: | + cf -v + cf8 -v + + - name: Test Version Match + run: cf -v | grep -q "$BUILD_VERSION" + +# vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=2 fdm=indent nospell: diff --git a/.gitignore b/.gitignore index 85d8d090b19..bdd84341efc 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,8 @@ tmp/ .hg/ +.envrc + *.test /tags diff --git a/Makefile b/Makefile index 195d9286aa3..aa70bbe8001 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ -SHELL := env PATH=$(PATH) /bin/bash CF_DIAL_TIMEOUT ?= 15 NODES ?= 10 PACKAGES ?= api actor command types util version integration/helpers @@ -17,15 +16,13 @@ REQUIRED_FOR_STATIC_BINARY =-a -tags "netgo" -installsuffix netgo GOSRC = $(shell find . -name "*.go" ! -name "*test.go" ! -name "*fake*" ! -path "./integration/*") UNAME_S := $(shell uname -s) - -TARGET = v7 SLOW_SPEC_THRESHOLD=120 -GINKGO_FLAGS=-r -randomizeAllSpecs -requireSuite -GINKGO_INT_FLAGS=$(GINKGO_FLAGS) -slowSpecThreshold $(SLOW_SPEC_THRESHOLD) +GINKGO_FLAGS ?= -r -randomizeAllSpecs -requireSuite +GINKGO_INT_FLAGS = $(GINKGO_FLAGS) -slowSpecThreshold $(SLOW_SPEC_THRESHOLD) ginkgo_int = ginkgo $(GINKGO_INT_FLAGS) -GINKGO_UNITS_FLAGS=$(GINKGO_FLAGS) -randomizeSuites -p +GINKGO_UNITS_FLAGS = $(GINKGO_FLAGS) -randomizeSuites -p ginkgo_units = ginkgo $(GINKGO_UNITS_FLAGS) GOFLAGS := -mod=mod @@ -88,7 +85,7 @@ integration-shared-experimental: build integration-cleanup ## Run experimental i ive: integration-versioned-experimental integration-experimental-versioned: integration-versioned-experimental integration-versioned-experimental: build integration-cleanup ## Run experimental integration tests that are specific to your CLI version - $(ginkgo_int) -nodes $(NODES) integration/$(TARGET)/experimental + $(ginkgo_int) -nodes $(NODES) integration/v7/experimental ig: integration-global integration-global: build integration-cleanup integration-shared-global integration-global-versioned ## Run all unparallelizable integration tests that make cross-cutting changes to their test CF foundation @@ -101,7 +98,7 @@ integration-shared-global: build integration-cleanup ## Serially run integration ivg: integration-versioned-global integration-global-versioned: integration-versioned-global integration-versioned-global: build integration-cleanup ## Serially run integration tests that make cross-cutting changes to their test CF foundation and are specific to your CLI version - $(ginkgo_int) integration/$(TARGET)/global + $(ginkgo_int) integration/v7/global ii: integration-isolated integration-isolated: build integration-cleanup integration-shared-isolated integration-isolated-versioned ## Run all parallel-enabled integration tests, both versioned and shared across versions @@ -121,14 +118,14 @@ integration-shared-performance: build integration-cleanup ivi: integration-versioned-isolated integration-isolated-versioned: integration-versioned-isolated integration-versioned-isolated: build integration-cleanup ## Run all parallel-enabled integration tests, both versioned and shared across versions - $(ginkgo_int) -nodes $(NODES) integration/$(TARGET)/isolated + $(ginkgo_int) -nodes $(NODES) integration/v7/isolated integration-plugin: build integration-cleanup ## Run all plugin-related integration tests $(ginkgo_int) -nodes $(NODES) integration/shared/plugin ip: integration-push integration-push: build integration-cleanup ## Run all push-related integration tests - $(ginkgo_int) -nodes $(NODES) integration/$(TARGET)/push + $(ginkgo_int) -nodes $(NODES) integration/v7/push integration-selfcontained: build $(ginkgo_int) -nodes $(NODES) integration/v7/selfcontained diff --git a/bin/bump-version b/bin/bump-version index 7bb7882877a..b4693dc4e90 100755 --- a/bin/bump-version +++ b/bin/bump-version @@ -1,13 +1,17 @@ #!/usr/bin/env bash -set -e +set -eo pipefail component=$1 +script_dir=$(dirname $0) +version_file="$script_dir/../BUILD_VERSION" -old_version=$(cat BUILD_VERSION) +old_version=$(cat $version_file) major=$(echo $old_version | cut -d'.' -f 1) minor=$(echo $old_version | cut -d'.' -f 2) -patch=$(echo $old_version | cut -d'.' -f 3) +patchAndSuffix=$(echo $old_version | cut -d'.' -f 3) +patch=$(echo $patchAndSuffix | cut -d'-' -f 1) +suffix=$(echo $patchAndSuffix | cut -s -d'-' -f 2) case "$component" in major ) @@ -28,14 +32,19 @@ case "$component" in exit 1 ;; esac +set -u version=$major.$minor.$patch +if [ ! -z "$suffix" ]; then + version="${version}-${suffix}" +fi + echo "Updating BUILD_VERSION file to $version" -echo $version > BUILD_VERSION +echo $version > $version_file echo "Committing change" git reset . -git add BUILD_VERSION +git add $version_file git commit -m "Bump version to $version" diff --git a/bin/generate-release-notes b/bin/generate-release-notes index f1aad7ebcc5..0d7d8649681 100755 --- a/bin/generate-release-notes +++ b/bin/generate-release-notes @@ -5,7 +5,7 @@ VERSION=$(cat BUILD_VERSION) cat <<-NOTES Package Manager Installation ---------- -- [apt-get, yum, homebrew](https://github.com/cloudfoundry/cli#installing-using-a-package-manager) +- [apt-get, yum, homebrew](https://github.com/cloudfoundry/cli#getting-started) Installers ---------- From 3f548444b2f9ceeebbcf0bb30abcaefceef67bc5 Mon Sep 17 00:00:00 2001 From: gururajsh <97255381+gururajsh@users.noreply.github.com> Date: Tue, 17 May 2022 15:26:56 -0400 Subject: [PATCH 011/543] Units job addition --- .github/workflows/integration.yml | 165 ++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 .github/workflows/integration.yml diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 00000000000..7352a88a6e5 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,165 @@ +name: Integration Tests + +on: + push: + branches: + - "gha-integration" + +# permissions: +# contents: write + +defaults: + run: + shell: bash + +jobs: + shared-values: + name: Shared Values + runs-on: ubuntu-latest + outputs: + go-version: ${{ steps.set-go-version.outputs.go-version }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - id: set-go-version + run: | + echo "::set-output name=go-version::1.17" + + units: + needs: shared-values + runs-on: ubuntu-latest + steps: + - name: Checkout cli + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ needs.shared-values.outputs.go-version }} + # - name: lint + - name: Linux Unit tests + run: | + export TARGET_V7=true + export GOPATH=$PWD/gopath + export PATH=$GOPATH/bin:$PATH + export GOFLAGS="-mod=mod" + + cd $GOPATH/src/code.cloudfoundry.org/cli + + export LC_ALL="en_US.UTF-8" + + go version + + go get -u github.com/onsi/ginkgo/ginkgo + ginkgo version + + # we actually want the grep to exit 1 (i.e., no matches found) here + set +e + echo -e "\n Checking that packages have been go formatted..." + gofmt -l . | grep -v -e ^vendor + # if we match anything, we have unformatted files + if [[ $? -eq 0 ]]; then + echo "Found unlinted files, please run 'make format' and push again." + exit 1 + fi + set -e + + make units-full + + + + + # lint: + # name: Lint code + # runs-on: ubuntu-latest + # needs: shared-values + # steps: + + # - name: Checkout + # uses: actions/checkout@v2 + + # - name: Set up Go + # uses: actions/setup-go@v2 + # with: + # go-version: ${{ needs.shared-values.outputs.go-version }} + + # - name: Run go fmt + # run: go fmt && git diff --exit-code + + # units: + # name: Units + # needs: shared-values + # strategy: + # matrix: + # os: + # - ubuntu-latest + # - ubuntu-18.04 + # - macos-latest + # - macos-10.15 + # runs-on: ${{ matrix.os }} + # steps: + + # - name: Checkout + # uses: actions/checkout@v2 + + # - name: Set up Go + # uses: actions/setup-go@v2 + # with: + # go-version: ${{ needs.shared-values.outputs.go-version }} + + # - name: Set up Test + # run: | + # go get -u github.com/onsi/ginkgo/ginkgo + # go get github.com/onsi/gomega/matchers@v1.10.5 + + # - name: Run Linux Units + # run: make units + # env: + # ACK_GINKGO_RC: true + + # units-windows: + # name: Units Windows + # strategy: + # matrix: + # os: + # - windows-2022 + # - windows-2019 + # - windows-2016 + # runs-on: ${{ matrix.os }} + # needs: shared-values + # defaults: + # run: + # shell: pwsh + # steps: + + # - name: Checkout + # uses: actions/checkout@v2 + + # - name: Set up Go + # uses: actions/setup-go@v2 + # with: + # go-version: ${{ needs.shared-values.outputs.go-version }} + + # - name: Set up Test + # run: | + # go get -u github.com/onsi/ginkgo/ginkgo + # go get github.com/onsi/gomega/matchers@v1.10.5 + + # - name: Get build-time dependencies + # run: | + # choco install --no-progress --limit-output -y make + + # - name: Run Windows Units + # env: + # ACK_GINKGO_RC: true + # run: > + # # Get-Command make + # # Get-Item Makefile + # # make units-non-plugin + + # ginkgo -r -p + # -randomizeAllSpecs + # -randomizeSuites + # -skipPackage integration,cf\ssh,plugin,cf\actors\plugin,cf\commands\plugin,cf\actors\plugin,util\randomword + # -flakeAttempts=2 + +# vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=2 fdm=indent nospell: From a52411a68b96c15db3432e83207965c74d057c16 Mon Sep 17 00:00:00 2001 From: gururajsh <97255381+gururajsh@users.noreply.github.com> Date: Tue, 17 May 2022 15:42:11 -0400 Subject: [PATCH 012/543] Revert "Merge branch 'gha-integration' into master" This reverts commit 683d87e6c5c22da2efb54d6bb26ab2357b9b4b5d, reversing changes made to b04b29dabc470cf9d9858e6c471e996064451b87. --- .github/workflows/integration.yml | 165 ------------------------------ 1 file changed, 165 deletions(-) delete mode 100644 .github/workflows/integration.yml diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml deleted file mode 100644 index 7352a88a6e5..00000000000 --- a/.github/workflows/integration.yml +++ /dev/null @@ -1,165 +0,0 @@ -name: Integration Tests - -on: - push: - branches: - - "gha-integration" - -# permissions: -# contents: write - -defaults: - run: - shell: bash - -jobs: - shared-values: - name: Shared Values - runs-on: ubuntu-latest - outputs: - go-version: ${{ steps.set-go-version.outputs.go-version }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - id: set-go-version - run: | - echo "::set-output name=go-version::1.17" - - units: - needs: shared-values - runs-on: ubuntu-latest - steps: - - name: Checkout cli - uses: actions/checkout@v2 - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: ${{ needs.shared-values.outputs.go-version }} - # - name: lint - - name: Linux Unit tests - run: | - export TARGET_V7=true - export GOPATH=$PWD/gopath - export PATH=$GOPATH/bin:$PATH - export GOFLAGS="-mod=mod" - - cd $GOPATH/src/code.cloudfoundry.org/cli - - export LC_ALL="en_US.UTF-8" - - go version - - go get -u github.com/onsi/ginkgo/ginkgo - ginkgo version - - # we actually want the grep to exit 1 (i.e., no matches found) here - set +e - echo -e "\n Checking that packages have been go formatted..." - gofmt -l . | grep -v -e ^vendor - # if we match anything, we have unformatted files - if [[ $? -eq 0 ]]; then - echo "Found unlinted files, please run 'make format' and push again." - exit 1 - fi - set -e - - make units-full - - - - - # lint: - # name: Lint code - # runs-on: ubuntu-latest - # needs: shared-values - # steps: - - # - name: Checkout - # uses: actions/checkout@v2 - - # - name: Set up Go - # uses: actions/setup-go@v2 - # with: - # go-version: ${{ needs.shared-values.outputs.go-version }} - - # - name: Run go fmt - # run: go fmt && git diff --exit-code - - # units: - # name: Units - # needs: shared-values - # strategy: - # matrix: - # os: - # - ubuntu-latest - # - ubuntu-18.04 - # - macos-latest - # - macos-10.15 - # runs-on: ${{ matrix.os }} - # steps: - - # - name: Checkout - # uses: actions/checkout@v2 - - # - name: Set up Go - # uses: actions/setup-go@v2 - # with: - # go-version: ${{ needs.shared-values.outputs.go-version }} - - # - name: Set up Test - # run: | - # go get -u github.com/onsi/ginkgo/ginkgo - # go get github.com/onsi/gomega/matchers@v1.10.5 - - # - name: Run Linux Units - # run: make units - # env: - # ACK_GINKGO_RC: true - - # units-windows: - # name: Units Windows - # strategy: - # matrix: - # os: - # - windows-2022 - # - windows-2019 - # - windows-2016 - # runs-on: ${{ matrix.os }} - # needs: shared-values - # defaults: - # run: - # shell: pwsh - # steps: - - # - name: Checkout - # uses: actions/checkout@v2 - - # - name: Set up Go - # uses: actions/setup-go@v2 - # with: - # go-version: ${{ needs.shared-values.outputs.go-version }} - - # - name: Set up Test - # run: | - # go get -u github.com/onsi/ginkgo/ginkgo - # go get github.com/onsi/gomega/matchers@v1.10.5 - - # - name: Get build-time dependencies - # run: | - # choco install --no-progress --limit-output -y make - - # - name: Run Windows Units - # env: - # ACK_GINKGO_RC: true - # run: > - # # Get-Command make - # # Get-Item Makefile - # # make units-non-plugin - - # ginkgo -r -p - # -randomizeAllSpecs - # -randomizeSuites - # -skipPackage integration,cf\ssh,plugin,cf\actors\plugin,cf\commands\plugin,cf\actors\plugin,util\randomword - # -flakeAttempts=2 - -# vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=2 fdm=indent nospell: From 907c2dc0a0349caced1fedaba46d0d99b528abfd Mon Sep 17 00:00:00 2001 From: Shwetha Gururaj Date: Mon, 6 Jun 2022 18:53:15 +0000 Subject: [PATCH 013/543] Bump golang to 1.18 Co-authored-by: Alexander Berezovsky --- .github/workflows/build-sign-upload.yml | 2 +- .github/workflows/units.yml | 2 +- go.mod | 10 +++++----- go.sum | 21 ++++++++------------- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-sign-upload.yml b/.github/workflows/build-sign-upload.yml index 37bbdeaccdf..72bd1212371 100644 --- a/.github/workflows/build-sign-upload.yml +++ b/.github/workflows/build-sign-upload.yml @@ -49,7 +49,7 @@ jobs: - name: Set go version id: set-go-version - run: echo "::set-output name=go-version::1.17" + run: echo "::set-output name=go-version::1.18" # This is for debugging. It's equivalent to fly intecept # - name: Setup upterm session diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index 381e21aa522..9ca5b80981e 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v2 - id: set-go-version run: | - echo "::set-output name=go-version::1.17" + echo "::set-output name=go-version::1.18" lint: name: Lint code diff --git a/go.mod b/go.mod index e1d57e6a039..5169eb5ea3c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module code.cloudfoundry.org/cli -go 1.17 +go 1.18 require ( code.cloudfoundry.org/bytefmt v0.0.0-20170428003108-f4415fafc561 @@ -39,8 +39,8 @@ require ( github.com/sirupsen/logrus v1.2.0 github.com/tedsuo/rata v1.0.1-0.20170830210128-07d200713958 github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 - golang.org/x/net v0.0.0-20211013171255-e13a2654a71e + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e + golang.org/x/net v0.0.0-20220531201128-c960675eff93 golang.org/x/text v0.3.7 gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/yaml.v2 v2.4.0 @@ -84,8 +84,8 @@ require ( github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00 // indirect golang.org/x/mod v0.4.2 // indirect golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect - golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect diff --git a/go.sum b/go.sum index 46324f18f2e..dc1993b5db4 100644 --- a/go.sum +++ b/go.sum @@ -240,7 +240,6 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 h1:rgxjzoDmDXw5q8HONgyHhBas4to0/XWRo/gPpJhsUNQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0/go.mod h1:qrJPVzv9YlhsrxJc3P/Q85nr0w1lIRikTl4JlhdDH5w= @@ -380,7 +379,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -396,8 +394,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -465,13 +463,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211013171255-e13a2654a71e h1:Xj+JO91noE97IN6F/7WZxzC5QE6yENAQPrwIYhW3bsA= -golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA= +golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -531,16 +527,15 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From a73e102418870909d0813ca20cc7c47d5212c17d Mon Sep 17 00:00:00 2001 From: Shwetha Gururaj Date: Mon, 6 Jun 2022 19:27:49 +0000 Subject: [PATCH 014/543] Parse pipelines go version from go.mod We want to use single source of truth go.mod, instead of configuration scattered across multiple places. Co-authored-by: Alexander Berezovsky --- .github/workflows/build-sign-upload.yml | 9 ++++++--- .github/workflows/units.yml | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-sign-upload.yml b/.github/workflows/build-sign-upload.yml index 72bd1212371..6089c77c323 100644 --- a/.github/workflows/build-sign-upload.yml +++ b/.github/workflows/build-sign-upload.yml @@ -41,15 +41,18 @@ jobs: exit 0 - - name: Set build version + - name: Parse Build Version id: set-build-version run: | version=$(cat BUILD_VERSION) echo "::set-output name=build-version::$version" - - name: Set go version + - name: Parse Golang Version id: set-go-version - run: echo "::set-output name=go-version::1.18" + run: | + go_version=($(grep -E '^go 1\.[[:digit:]]{1,2}' go.mod)) + echo "golang version: ${go_version[1]}" + echo "::set-output name=go-version::${go_version[1]}" # This is for debugging. It's equivalent to fly intecept # - name: Setup upterm session diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index 9ca5b80981e..2edf95a29a7 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -19,11 +19,16 @@ jobs: outputs: go-version: ${{ steps.set-go-version.outputs.go-version }} steps: + - name: Checkout uses: actions/checkout@v2 + - id: set-go-version + name: Parse Golang Version run: | - echo "::set-output name=go-version::1.18" + go_version=($(grep -E '^go 1\.[[:digit:]]{1,2}' go.mod)) + echo "golang version: ${go_version[1]}" + echo "::set-output name=go-version::${go_version[1]}" lint: name: Lint code From 06cdfbc8a439eef3d5810b5bb5ec019f328384f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jun 2022 16:48:48 -0500 Subject: [PATCH 015/543] Bump sinatra from 2.0.4 to 2.2.0 in /fixtures/applications/example-app (#2274) Bumps [sinatra](https://github.com/sinatra/sinatra) from 2.0.4 to 2.2.0. - [Release notes](https://github.com/sinatra/sinatra/releases) - [Changelog](https://github.com/sinatra/sinatra/blob/master/CHANGELOG.md) - [Commits](https://github.com/sinatra/sinatra/compare/v2.0.4...v2.2.0) --- updated-dependencies: - dependency-name: sinatra dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- fixtures/applications/example-app/Gemfile.lock | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fixtures/applications/example-app/Gemfile.lock b/fixtures/applications/example-app/Gemfile.lock index 6c625c63d55..4c418edc0ba 100644 --- a/fixtures/applications/example-app/Gemfile.lock +++ b/fixtures/applications/example-app/Gemfile.lock @@ -1,16 +1,18 @@ GEM remote: https://rubygems.org/ specs: - mustermann (1.0.3) + mustermann (1.1.1) + ruby2_keywords (~> 0.0.1) rack (2.2.3) - rack-protection (2.0.4) + rack-protection (2.2.0) rack - sinatra (2.0.4) + ruby2_keywords (0.0.5) + sinatra (2.2.0) mustermann (~> 1.0) - rack (~> 2.0) - rack-protection (= 2.0.4) + rack (~> 2.2) + rack-protection (= 2.2.0) tilt (~> 2.0) - tilt (2.0.9) + tilt (2.0.10) PLATFORMS ruby From 0feb9c2f89cdd0eee02151b019ed2f0fa3596c23 Mon Sep 17 00:00:00 2001 From: Shwetha Gururaj Date: Wed, 15 Jun 2022 18:35:45 +0000 Subject: [PATCH 016/543] Skipping SSL validation tests for macOS Starting golang 1.18 system APIs are used for certificate verification on macOS instead of built-in go verifier. Which makes behavior similar to Windows. Since we are skipping this test for Windows due to difficulties with execution order we believe it's fair to skip it in macOS too and rely on Linux test. References: - [Changes to x509 in Go 1.18 conversation](https://groups.google.com/g/golang-nuts/c/RGghq2gTWss/m/7GsudTfCAgAJ) - https://cs.opensource.google/go/go/+/master:src/crypto/x509/root_darwin.go;l=52 - https://cs.opensource.google/go/go/+/master:src/crypto/x509/verify.go;l=766 Co-authored-by: Alexander Berezovsky --- .github/workflows/units.yml | 8 ++++---- api/cloudcontroller/cloud_controller_connection_test.go | 7 +++++-- api/plugin/plugin_connection_test.go | 7 +++++-- api/uaa/uaa_connection_test.go | 4 ++++ cf/net/gateway_test.go | 6 ++++++ cf/util/testhelpers/net/make_tls_cert.go | 2 +- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index 2edf95a29a7..db2f329155e 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -70,8 +70,8 @@ jobs: - name: Set up Test run: | - go get -u github.com/onsi/ginkgo/ginkgo - go get github.com/onsi/gomega/matchers@v1.10.5 + go install github.com/onsi/ginkgo/ginkgo@v1.16.4 + go install github.com/onsi/gomega/matchers - name: Run Linux Units run: make units @@ -103,8 +103,8 @@ jobs: - name: Set up Test run: | - go get -u github.com/onsi/ginkgo/ginkgo - go get github.com/onsi/gomega/matchers@v1.10.5 + go install github.com/onsi/ginkgo/ginkgo@v1.16.4 + go install github.com/onsi/gomega/matchers - name: Get build-time dependencies run: | diff --git a/api/cloudcontroller/cloud_controller_connection_test.go b/api/cloudcontroller/cloud_controller_connection_test.go index c0dbbd5c3d7..747d6ff71f9 100644 --- a/api/cloudcontroller/cloud_controller_connection_test.go +++ b/api/cloudcontroller/cloud_controller_connection_test.go @@ -253,6 +253,9 @@ var _ = Describe("Cloud Controller Connection", func() { When("the server does not have a verified certificate", func() { Context("skipSSLValidation is false", func() { BeforeEach(func() { + if runtime.GOOS == "darwin" { + Skip("ssl verification is different on darwin") + } server.AppendHandlers( CombineHandlers( VerifyRequest(http.MethodGet, "/v2/foo"), @@ -277,8 +280,8 @@ var _ = Describe("Cloud Controller Connection", func() { When("the server's certificate does not match the hostname", func() { Context("skipSSLValidation is false", func() { BeforeEach(func() { - if runtime.GOOS == "windows" { - Skip("ssl validation has a different order on windows, will not be returned properly") + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + Skip("ssl validation has a different order on windows/darwin, will not be returned properly") } server.AppendHandlers( CombineHandlers( diff --git a/api/plugin/plugin_connection_test.go b/api/plugin/plugin_connection_test.go index 6415fdc7b9c..5e1a2fd12b6 100644 --- a/api/plugin/plugin_connection_test.go +++ b/api/plugin/plugin_connection_test.go @@ -155,6 +155,9 @@ var _ = Describe("Plugin Connection", func() { When("the server does not have a verified certificate", func() { Context("skipSSLValidation is false", func() { BeforeEach(func() { + if runtime.GOOS == "darwin" { + Skip("ssl verification is different on darwin") + } server.AppendHandlers( CombineHandlers( VerifyRequest(http.MethodGet, "/list"), @@ -178,8 +181,8 @@ var _ = Describe("Plugin Connection", func() { When("the server's certificate does not match the hostname", func() { Context("skipSSLValidation is false", func() { BeforeEach(func() { - if runtime.GOOS == "windows" { - Skip("ssl validation has a different order on windows, will not be returned properly") + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + Skip("ssl validation has a different order on windows/darwin, will not be returned properly") } server.AppendHandlers( CombineHandlers( diff --git a/api/uaa/uaa_connection_test.go b/api/uaa/uaa_connection_test.go index ed49ccdac82..129c72cc178 100644 --- a/api/uaa/uaa_connection_test.go +++ b/api/uaa/uaa_connection_test.go @@ -3,6 +3,7 @@ package uaa_test import ( "fmt" "net/http" + "runtime" . "code.cloudfoundry.org/cli/api/uaa" . "github.com/onsi/ginkgo" @@ -120,6 +121,9 @@ var _ = Describe("UAA Connection", func() { When("the server does not have a verified certificate", func() { Context("skipSSLValidation is false", func() { BeforeEach(func() { + if runtime.GOOS == "darwin" { + Skip("ssl verification is different on darwin") + } server.AppendHandlers( CombineHandlers( VerifyRequest(http.MethodGet, "/v2/foo"), diff --git a/cf/net/gateway_test.go b/cf/net/gateway_test.go index bdc15877344..88c957ca0b2 100644 --- a/cf/net/gateway_test.go +++ b/cf/net/gateway_test.go @@ -528,7 +528,13 @@ var _ = Describe("Gateway", func() { }) Context("when SSL validation is enabled", func() { + BeforeEach(func() { + if runtime.GOOS == "darwin" { + Skip("ssl verification is different on darwin") + } + }) It("returns an invalid cert error if the server's CA is unknown (e.g. cert is self-signed)", func() { + apiServer.TLS.Certificates = []tls.Certificate{testnet.MakeSelfSignedTLSCert()} _, apiErr := ccGateway.PerformRequest(request) diff --git a/cf/util/testhelpers/net/make_tls_cert.go b/cf/util/testhelpers/net/make_tls_cert.go index ab55003fb0d..d681898d6e2 100644 --- a/cf/util/testhelpers/net/make_tls_cert.go +++ b/cf/util/testhelpers/net/make_tls_cert.go @@ -30,7 +30,7 @@ func MakeUnauthorizedTLSCert() tls.Certificate { } func generateCert(hosts []string, notAfter time.Time, isAuthorizedToSign bool) tls.Certificate { - priv, err := rsa.GenerateKey(rand.Reader, 1024) + priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { panic(err) } From 82f39a3eaa8434e9d6be57d908fee4f4a3014087 Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Thu, 16 Jun 2022 15:22:23 -0700 Subject: [PATCH 017/543] Update Contributing to use go 1.18 - does not build with go 1.16 Co-authored-by: Rebecca Roberts Co-authored-by: Michael Oleske --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index fcdfadb538d..c37025fe4b0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -45,7 +45,7 @@ that conveys the intent of your change. # Development Environment Setup -## Install Golang 1.16 +## Install Golang 1.18 Documentation on installing GoLang can be found [here](https://golang.org/doc/install). While the CF CLI might be compatible with other versions of GoLang, this is the only From 17feaa9c3304184db0df7597010fae892298dc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Diego=20Gonz=C3=A1lez?= <25335192+jdgonzaleza@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:04:07 -0500 Subject: [PATCH 018/543] Implements new command `share-route` (#2288) This command is used for sharing a route in between two spaces in order to facilitate the movement of apps in between spaces. --- actor/v7action/cloud_controller_client.go | 1 + actor/v7action/route.go | 5 +- .../fake_cloud_controller_client.go | 80 +++++ .../ccv3/internal/api_routes.go | 2 + api/cloudcontroller/ccv3/route.go | 25 ++ command/common/command_list_v7.go | 1 + command/common/internal/help_all_display.go | 1 + command/v7/actor.go | 1 + command/v7/share_route_command.go | 101 +++++++ command/v7/share_route_command_test.go | 279 ++++++++++++++++++ command/v7/v7fakes/fake_actor.go | 80 +++++ .../v7/isolated/share_route_command_test.go | 204 +++++++++++++ 12 files changed, 779 insertions(+), 1 deletion(-) create mode 100644 command/v7/share_route_command.go create mode 100644 command/v7/share_route_command_test.go create mode 100644 integration/v7/isolated/share_route_command_test.go diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index 5bca12c73b0..756c0528ffd 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -149,6 +149,7 @@ type CloudControllerClient interface { SetApplicationDroplet(appGUID string, dropletGUID string) (resources.Relationship, ccv3.Warnings, error) SharePrivateDomainToOrgs(domainGuid string, sharedOrgs ccv3.SharedOrgs) (ccv3.Warnings, error) ShareServiceInstanceToSpaces(serviceInstanceGUID string, spaceGUIDs []string) (resources.RelationshipList, ccv3.Warnings, error) + ShareRoute(routeGUID string, spaceGUID string) (ccv3.Warnings, error) TargetCF(settings ccv3.TargetSettings) UnbindSecurityGroupRunningSpace(securityGroupGUID string, spaceGUID string) (ccv3.Warnings, error) UnbindSecurityGroupStagingSpace(securityGroupGUID string, spaceGUID string) (ccv3.Warnings, error) diff --git a/actor/v7action/route.go b/actor/v7action/route.go index e91d5434473..16f7bc9f4ce 100644 --- a/actor/v7action/route.go +++ b/actor/v7action/route.go @@ -406,7 +406,10 @@ func (actor Actor) UnmapRoute(routeGUID string, destinationGUID string) (Warning warnings, err := actor.CloudControllerClient.UnmapRoute(routeGUID, destinationGUID) return Warnings(warnings), err } - +func (actor Actor) ShareRoute(routeGUID string, spaceGUID string) (Warnings, error) { + warnings, err := actor.CloudControllerClient.ShareRoute(routeGUID, spaceGUID) + return Warnings(warnings), err +} func (actor Actor) GetApplicationRoutes(appGUID string) ([]resources.Route, Warnings, error) { allWarnings := Warnings{} diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index bc955a70bba..15a378271c2 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -2076,6 +2076,20 @@ type FakeCloudControllerClient struct { result1 ccv3.Warnings result2 error } + ShareRouteStub func(string, string) (ccv3.Warnings, error) + shareRouteMutex sync.RWMutex + shareRouteArgsForCall []struct { + arg1 string + arg2 string + } + shareRouteReturns struct { + result1 ccv3.Warnings + result2 error + } + shareRouteReturnsOnCall map[int]struct { + result1 ccv3.Warnings + result2 error + } ShareServiceInstanceToSpacesStub func(string, []string) (resources.RelationshipList, ccv3.Warnings, error) shareServiceInstanceToSpacesMutex sync.RWMutex shareServiceInstanceToSpacesArgsForCall []struct { @@ -11688,6 +11702,70 @@ func (fake *FakeCloudControllerClient) SharePrivateDomainToOrgsReturnsOnCall(i i }{result1, result2} } +func (fake *FakeCloudControllerClient) ShareRoute(arg1 string, arg2 string) (ccv3.Warnings, error) { + fake.shareRouteMutex.Lock() + ret, specificReturn := fake.shareRouteReturnsOnCall[len(fake.shareRouteArgsForCall)] + fake.shareRouteArgsForCall = append(fake.shareRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("ShareRoute", []interface{}{arg1, arg2}) + fake.shareRouteMutex.Unlock() + if fake.ShareRouteStub != nil { + return fake.ShareRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.shareRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeCloudControllerClient) ShareRouteCallCount() int { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + return len(fake.shareRouteArgsForCall) +} + +func (fake *FakeCloudControllerClient) ShareRouteCalls(stub func(string, string) (ccv3.Warnings, error)) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = stub +} + +func (fake *FakeCloudControllerClient) ShareRouteArgsForCall(i int) (string, string) { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + argsForCall := fake.shareRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCloudControllerClient) ShareRouteReturns(result1 ccv3.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + fake.shareRouteReturns = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeCloudControllerClient) ShareRouteReturnsOnCall(i int, result1 ccv3.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + if fake.shareRouteReturnsOnCall == nil { + fake.shareRouteReturnsOnCall = make(map[int]struct { + result1 ccv3.Warnings + result2 error + }) + } + fake.shareRouteReturnsOnCall[i] = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeCloudControllerClient) ShareServiceInstanceToSpaces(arg1 string, arg2 []string) (resources.RelationshipList, ccv3.Warnings, error) { var arg2Copy []string if arg2 != nil { @@ -14659,6 +14737,8 @@ func (fake *FakeCloudControllerClient) Invocations() map[string][][]interface{} defer fake.setApplicationDropletMutex.RUnlock() fake.sharePrivateDomainToOrgsMutex.RLock() defer fake.sharePrivateDomainToOrgsMutex.RUnlock() + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() fake.shareServiceInstanceToSpacesMutex.RLock() defer fake.shareServiceInstanceToSpacesMutex.RUnlock() fake.targetCFMutex.RLock() diff --git a/api/cloudcontroller/ccv3/internal/api_routes.go b/api/cloudcontroller/ccv3/internal/api_routes.go index 8f2239316c8..7029d8073fa 100644 --- a/api/cloudcontroller/ccv3/internal/api_routes.go +++ b/api/cloudcontroller/ccv3/internal/api_routes.go @@ -172,6 +172,7 @@ const ( PostUserRequest = "PostUser" PutTaskCancelRequest = "PutTaskCancel" SharePrivateDomainRequest = "SharePrivateDomainRequest" + ShareRouteRequest = "ShareRouteRequest" UnmapRouteRequest = "UnmapRoute" WhoAmI = "WhoAmI" ) @@ -277,6 +278,7 @@ var APIRoutes = map[string]Route{ MapRouteRequest: {Path: "/v3/routes/:route_guid/destinations", Method: http.MethodPost}, UnmapRouteRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodDelete}, PatchDestinationRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodPatch}, + ShareRouteRequest: {Path: "/v3/routes/:route_guid/relationships/shared_spaces", Method: http.MethodPost}, GetSecurityGroupsRequest: {Path: "/v3/security_groups", Method: http.MethodGet}, PostSecurityGroupRequest: {Path: "/v3/security_groups", Method: http.MethodPost}, DeleteSecurityGroupRequest: {Path: "/v3/security_groups/:security_group_guid", Method: http.MethodDelete}, diff --git a/api/cloudcontroller/ccv3/route.go b/api/cloudcontroller/ccv3/route.go index b40aed980c9..d7ad9a2c182 100644 --- a/api/cloudcontroller/ccv3/route.go +++ b/api/cloudcontroller/ccv3/route.go @@ -148,3 +148,28 @@ func (client Client) UpdateDestination(routeGUID string, destinationGUID string, }) return warnings, err } + +func (client Client) ShareRoute(routeGUID string, spaceGUID string) (Warnings, error) { + type space struct { + GUID string `json:"guid"` + } + + type body struct { + Data []space `json:"data"` + } + + requestBody := body{ + Data: []space{ + {GUID: spaceGUID}, + }, + } + + var responseBody resources.Build + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.ShareRouteRequest, + URIParams: internal.Params{"route_guid": routeGUID}, + RequestBody: &requestBody, + ResponseBody: &responseBody, + }) + return warnings, err +} diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index 83300833d79..fada59bcb02 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -157,6 +157,7 @@ type commandList struct { SetStagingEnvironmentVariableGroup v7.SetStagingEnvironmentVariableGroupCommand `command:"set-staging-environment-variable-group" alias:"ssevg" description:"Pass parameters as JSON to create a staging environment variable group"` SharePrivateDomain v7.SharePrivateDomainCommand `command:"share-private-domain" description:"Share a private domain with a specific org"` ShareService v7.ShareServiceCommand `command:"share-service" description:"Share a service instance with another space"` + ShareRoute v7.ShareRouteCommand `command:"share-route" description:"Share a route in between spaces"` Space v7.SpaceCommand `command:"space" description:"Show space info"` SpaceQuota v7.SpaceQuotaCommand `command:"space-quota" description:"Show space quota info"` SpaceQuotas v7.SpaceQuotasCommand `command:"space-quotas" description:"List available space quotas"` diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index e6d19c3d859..8c6e9957f09 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -68,6 +68,7 @@ var HelpCategoryList = []HelpCategory{ {"create-route", "check-route", "map-route", "unmap-route", "delete-route"}, {"delete-orphaned-routes"}, {"update-destination"}, + {"share-route"}, }, }, { diff --git a/command/v7/actor.go b/command/v7/actor.go index b62365e4a90..64ef63dd1f0 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -216,6 +216,7 @@ type Actor interface { SetTarget(settings v7action.TargetSettings) (v7action.Warnings, error) SharePrivateDomain(domainName string, orgName string) (v7action.Warnings, error) ShareServiceInstanceToSpaceAndOrg(serviceInstanceName, targetedSpaceGUID, targetedOrgGUID string, sharedToDetails v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) + ShareRoute(routeGUID string, spaceGUID string) (v7action.Warnings, error) StageApplicationPackage(pkgGUID string) (resources.Build, v7action.Warnings, error) StagePackage(packageGUID, appName, spaceGUID string) (<-chan resources.Droplet, <-chan v7action.Warnings, <-chan error) StartApplication(appGUID string) (v7action.Warnings, error) diff --git a/command/v7/share_route_command.go b/command/v7/share_route_command.go new file mode 100644 index 00000000000..b1fdb2b6880 --- /dev/null +++ b/command/v7/share_route_command.go @@ -0,0 +1,101 @@ +package v7 + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/command/flag" +) + +type ShareRouteCommand struct { + BaseCommand + + RequireArgs flag.Domain `positional-args:"yes"` + Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` + Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` + DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` + DestinationSpace string `short:"s" description:"The space of the destination app (Default: targeted space)"` + + relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` +} + +func (cmd ShareRouteCommand) Usage() string { + return ` +Share an existing route in between two spaces: + CF_NAME share-route DOMAIN [--hostname HOSTNAME] [--path PATH] -s OTHER_SPACE [-o OTHER_ORG]` +} + +func (cmd ShareRouteCommand) Examples() string { + return ` +CF_NAME share-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo +CF_NAME share-route example.com --hostname myHost -s TargetSpace # myhost.example.com +CF_NAME share-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com` +} + +func (cmd ShareRouteCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + domain, warnings, err := cmd.Actor.GetDomainByName(cmd.RequireArgs.Domain) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + path := cmd.Path.Path + route, warnings, err := cmd.Actor.GetRouteByAttributes(domain, cmd.Hostname, path, 0) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + if _, ok := err.(actionerror.RouteNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + destinationOrgName := cmd.DestinationOrg + + if destinationOrgName == "" { + destinationOrgName = cmd.Config.TargetedOrganizationName() + } + + destinationOrg, warnings, err := cmd.Actor.GetOrganizationByName(destinationOrgName) + + if err != nil { + if _, ok := err.(actionerror.OrganizationNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + targetedSpace, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.DestinationSpace, destinationOrg.GUID) + if err != nil { + if _, ok := err.(actionerror.SpaceNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + url := desiredURL(domain.Name, cmd.Hostname, path, 0) + cmd.UI.DisplayTextWithFlavor("Sharing route {{.URL}} to space {{.DestinationSpace}} as {{.User}}", + map[string]interface{}{ + "URL": url, + "DestinationSpace": cmd.DestinationSpace, + "User": user.Name, + }) + warnings, err = cmd.Actor.ShareRoute( + route.GUID, + targetedSpace.GUID, + ) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + cmd.UI.DisplayOK() + + return nil +} diff --git a/command/v7/share_route_command_test.go b/command/v7/share_route_command_test.go new file mode 100644 index 00000000000..220efdafb30 --- /dev/null +++ b/command/v7/share_route_command_test.go @@ -0,0 +1,279 @@ +package v7_test + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/cf/errors" + "code.cloudfoundry.org/cli/command/commandfakes" + "code.cloudfoundry.org/cli/command/flag" + v7 "code.cloudfoundry.org/cli/command/v7" + "code.cloudfoundry.org/cli/command/v7/v7fakes" + "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/util/configv3" + "code.cloudfoundry.org/cli/util/ui" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("share-route Command", func() { + var ( + cmd v7.ShareRouteCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + binaryName string + executeErr error + domainName string + orgName string + spaceName string + hostname string + path string + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + binaryName = "myBinaryBread" + fakeConfig.BinaryNameReturns(binaryName) + + domainName = "some-domain.com" + orgName = "org-name-a" + spaceName = "space-name-a" + hostname = "myHostname" + path = "myPath" + + cmd = v7.ShareRouteCommand{ + BaseCommand: v7.BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + RequireArgs: flag.Domain{Domain: domainName}, + Hostname: hostname, + Path: flag.V7RoutePath{Path: path}, + DestinationOrg: orgName, + DestinationSpace: spaceName, + } + + fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space", GUID: "some-space-guid"}) + fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org"}) + fakeConfig.CurrentUserReturns(configv3.User{Name: "some-user"}, nil) + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + It("checks that target", func() { + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + + When("checking target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) + }) + It("returns an error", func() { + Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) + + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("the user is not logged in", func() { + var expectedErr error + + BeforeEach(func() { + expectedErr = errors.New("some current user error") + fakeActor.GetCurrentUserReturns(configv3.User{}, expectedErr) + }) + + It("return an error", func() { + Expect(executeErr).To(Equal(expectedErr)) + }) + }) + + When("the user is logged in and targeted", func() { + When("getting the domain errors", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns(resources.Domain{}, v7action.Warnings{"get-domain-warnings"}, errors.New("get-domain-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(executeErr).To(MatchError(errors.New("get-domain-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(0)) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(0)) + + Expect(fakeActor.ShareRouteCallCount()).To(Equal(0)) + }) + }) + + When("getting the domain succeeds", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns( + resources.Domain{Name: domainName, GUID: "domain-guid"}, + v7action.Warnings{"get-domain-warnings"}, + nil, + ) + }) + + When("the requested route does not exist", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.RouteNotFoundError{}, + ) + }) + + It("displays error message", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-route-warnings")) + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + }) + }) + + When("the requested route exists", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{GUID: "route-guid"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + When("getting the target space errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.SpaceNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetOrganizationByNameArgsForCall(0)).To(Equal(orgName)) + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + + Expect(fakeActor.ShareRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target org errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.OrganizationNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.ShareRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target space succeeds", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "space-guid-b"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + It("exits 0 with helpful message that the route is now being shared", func() { + Expect(executeErr).ShouldNot(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + Expect(fakeActor.ShareRouteCallCount()).To(Equal(1)) + }) + }) + }) + }) + }) +}) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 523de5ea272..1a950e4692e 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -2957,6 +2957,20 @@ type FakeActor struct { result1 v7action.Warnings result2 error } + ShareRouteStub func(string, string) (v7action.Warnings, error) + shareRouteMutex sync.RWMutex + shareRouteArgsForCall []struct { + arg1 string + arg2 string + } + shareRouteReturns struct { + result1 v7action.Warnings + result2 error + } + shareRouteReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } ShareServiceInstanceToSpaceAndOrgStub func(string, string, string, v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) shareServiceInstanceToSpaceAndOrgMutex sync.RWMutex shareServiceInstanceToSpaceAndOrgArgsForCall []struct { @@ -16243,6 +16257,70 @@ func (fake *FakeActor) SharePrivateDomainReturnsOnCall(i int, result1 v7action.W }{result1, result2} } +func (fake *FakeActor) ShareRoute(arg1 string, arg2 string) (v7action.Warnings, error) { + fake.shareRouteMutex.Lock() + ret, specificReturn := fake.shareRouteReturnsOnCall[len(fake.shareRouteArgsForCall)] + fake.shareRouteArgsForCall = append(fake.shareRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("ShareRoute", []interface{}{arg1, arg2}) + fake.shareRouteMutex.Unlock() + if fake.ShareRouteStub != nil { + return fake.ShareRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.shareRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) ShareRouteCallCount() int { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + return len(fake.shareRouteArgsForCall) +} + +func (fake *FakeActor) ShareRouteCalls(stub func(string, string) (v7action.Warnings, error)) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = stub +} + +func (fake *FakeActor) ShareRouteArgsForCall(i int) (string, string) { + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() + argsForCall := fake.shareRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeActor) ShareRouteReturns(result1 v7action.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + fake.shareRouteReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) ShareRouteReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.shareRouteMutex.Lock() + defer fake.shareRouteMutex.Unlock() + fake.ShareRouteStub = nil + if fake.shareRouteReturnsOnCall == nil { + fake.shareRouteReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.shareRouteReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) ShareServiceInstanceToSpaceAndOrg(arg1 string, arg2 string, arg3 string, arg4 v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) { fake.shareServiceInstanceToSpaceAndOrgMutex.Lock() ret, specificReturn := fake.shareServiceInstanceToSpaceAndOrgReturnsOnCall[len(fake.shareServiceInstanceToSpaceAndOrgArgsForCall)] @@ -19312,6 +19390,8 @@ func (fake *FakeActor) Invocations() map[string][][]interface{} { defer fake.setTargetMutex.RUnlock() fake.sharePrivateDomainMutex.RLock() defer fake.sharePrivateDomainMutex.RUnlock() + fake.shareRouteMutex.RLock() + defer fake.shareRouteMutex.RUnlock() fake.shareServiceInstanceToSpaceAndOrgMutex.RLock() defer fake.shareServiceInstanceToSpaceAndOrgMutex.RUnlock() fake.stageApplicationPackageMutex.RLock() diff --git a/integration/v7/isolated/share_route_command_test.go b/integration/v7/isolated/share_route_command_test.go new file mode 100644 index 00000000000..d3f8affe88b --- /dev/null +++ b/integration/v7/isolated/share_route_command_test.go @@ -0,0 +1,204 @@ +package isolated + +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" + . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("share route command", func() { + Context("Help", func() { + It("appears in cf help -a", func() { + session := helpers.CF("help", "-a") + + Eventually(session).Should(Exit(0)) + Expect(session).To(HaveCommandInCategoryWithDescription("share-route", "ROUTES", "Share a route in between spaces")) + }) + + It("displays the help information", func() { + session := helpers.CF("share-route", "--help") + Eventually(session).Should(Say(`NAME:`)) + Eventually(session).Should(Say("share-route - Share a route in between spaces")) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`USAGE:`)) + Eventually(session).Should(Say(`Share an existing route in between two spaces:`)) + Eventually(session).Should(Say(`cf share-route DOMAIN \[--hostname HOSTNAME\] \[--path PATH\] -s OTHER_SPACE \[-o OTHER_ORG\]`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`EXAMPLES:`)) + Eventually(session).Should(Say(`cf share-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo`)) + Eventually(session).Should(Say(`cf share-route example.com --hostname myHost -s TargetSpace # myhost.example.com`)) + Eventually(session).Should(Say(`cf share-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`OPTIONS:`)) + Eventually(session).Should(Say(`--hostname, -n\s+Hostname for the HTTP route \(required for shared domains\)`)) + Eventually(session).Should(Say(`--path\s+Path for the HTTP route`)) + Eventually(session).Should(Say(`-o\s+The org of the destination app \(Default: targeted org\)`)) + Eventually(session).Should(Say(`-s\s+The space of the destination app \(Default: targeted space\)`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`SEE ALSO:`)) + Eventually(session).Should(Say(`create-route, map-route, routes, unmap-route`)) + + Eventually(session).Should(Exit(0)) + }) + }) + + When("the environment is not setup correctly", func() { + It("fails with the appropriate errors", func() { + helpers.CheckEnvironmentTargetedCorrectly(true, false, ReadOnlyOrg, "share-route", "some-domain", "-s SOME_SPACE") + }) + }) + + When("the environment is set up conrrectly", func() { + var ( + userName string + orgName string + spaceName string + ) + + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionHTTP2RoutingV3) + orgName = helpers.NewOrgName() + spaceName = helpers.NewSpaceName() + + helpers.SetupCF(orgName, spaceName) + userName, _ = helpers.GetCredentials() + }) + + AfterEach(func() { + helpers.QuickDeleteOrg(orgName) + }) + + When("the domain extists", func() { + var ( + domainName string + targetSpaceName string + ) + + BeforeEach(func() { + domainName = helpers.NewDomainName() + }) + + When("the route exists", func() { + var ( + domain helpers.Domain + hostname string + ) + When("the target space exists in targeted org", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("shares the route to the destination space", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say(`Sharing route %s.%s to space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the target organization does not exist", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + targetOrgName = helpers.NewOrgName() + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 and an error message", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not share route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + + When("the target space exists in another existing org", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + targetOrgName = helpers.NewOrgName() + targetSpaceName = helpers.NewSpaceName() + helpers.CreateOrgAndSpace(targetOrgName, targetSpaceName) + helpers.SetupCF(orgName, spaceName) + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("shared the route to the destination space", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say(`Sharing route %s.%s to space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the space does not exist", func() { + var destinationSpaceName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + destinationSpaceName = "doesNotExistSpace" + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 with an error", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-s", destinationSpaceName) + Eventually(session).Should(Say("Can not share route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + + When("the route does not exist", func() { + var ( + domain helpers.Domain + hostname string + ) + + When("the target space exists", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.Create() + }) + + It("exits with 1 with an error message", func() { + session := helpers.CF("share-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not share route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + }) + }) +}) From e59699ecfad944d796cae7ff46e05661e91059d4 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Tue, 12 Jul 2022 05:32:36 +0200 Subject: [PATCH 019/543] fix lint errors (#2246) --- actor/pluginaction/uninstall.go | 2 +- api/cloudcontroller/ccv3/info.go | 5 ---- api/uaa/auth.go | 2 +- resources/application_resource.go | 3 ++- ...service_instance_usage_summary_resource.go | 2 +- resources/shared_to_spaces_resource_test.go | 3 ++- util/batcher/batcher_test.go | 4 ++-- util/railway/railway_test.go | 4 ++-- util/ui/ui.go | 24 +++++++++---------- util/ui/ui_test.go | 2 +- 10 files changed, 24 insertions(+), 27 deletions(-) diff --git a/actor/pluginaction/uninstall.go b/actor/pluginaction/uninstall.go index 485217256b9..4359d903a49 100644 --- a/actor/pluginaction/uninstall.go +++ b/actor/pluginaction/uninstall.go @@ -55,7 +55,7 @@ func (actor Actor) UninstallPlugin(uninstaller PluginUninstaller, name string) e break } - if err != nil && !os.IsNotExist(err) { + if !os.IsNotExist(err) { if _, isPathError := err.(*os.PathError); isPathError { time.Sleep(50 * time.Millisecond) } else { diff --git a/api/cloudcontroller/ccv3/info.go b/api/cloudcontroller/ccv3/info.go index 8a2e694a18c..564834a8ed1 100644 --- a/api/cloudcontroller/ccv3/info.go +++ b/api/cloudcontroller/ccv3/info.go @@ -95,11 +95,6 @@ func (info Info) Login() string { return info.Links.Login.HREF } -// ccv3Link returns the HREF of the CloudController v3 API. -func (info Info) ccV3Link() string { - return info.Links.CCV3.HREF -} - // ResourceLinks represents the information returned back from /v3. type ResourceLinks map[string]resources.APILink diff --git a/api/uaa/auth.go b/api/uaa/auth.go index fa208611e75..f12da64f163 100644 --- a/api/uaa/auth.go +++ b/api/uaa/auth.go @@ -109,7 +109,7 @@ func (client Client) getJtiFromToken(token string) (string, error) { } payload := make(map[string]interface{}) - json.Unmarshal(jsonPayload, &payload) + _ = json.Unmarshal(jsonPayload, &payload) jti, ok := payload["jti"].(string) if !ok { diff --git a/resources/application_resource.go b/resources/application_resource.go index b663ce8f8ce..00d0a0b95f8 100644 --- a/resources/application_resource.go +++ b/resources/application_resource.go @@ -1,9 +1,10 @@ package resources import ( + "encoding/json" + "code.cloudfoundry.org/cli/api/cloudcontroller" "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" - "encoding/json" ) // Application represents a Cloud Controller V3 Application. diff --git a/resources/service_instance_usage_summary_resource.go b/resources/service_instance_usage_summary_resource.go index 45be72edf13..20a5e147a76 100644 --- a/resources/service_instance_usage_summary_resource.go +++ b/resources/service_instance_usage_summary_resource.go @@ -13,7 +13,7 @@ func (s *ServiceInstanceUsageSummaryList) UnmarshalJSON(data []byte) error { } type ServiceInstanceUsageSummary struct { - SpaceGUID string `jsonry:"space.guid""` + SpaceGUID string `jsonry:"space.guid"` BoundAppCount int `jsonry:"bound_app_count"` } diff --git a/resources/shared_to_spaces_resource_test.go b/resources/shared_to_spaces_resource_test.go index 5fc486a931d..fc3322458f1 100644 --- a/resources/shared_to_spaces_resource_test.go +++ b/resources/shared_to_spaces_resource_test.go @@ -1,9 +1,10 @@ package resources_test import ( - "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" "encoding/json" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" + . "code.cloudfoundry.org/cli/resources" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" diff --git a/util/batcher/batcher_test.go b/util/batcher/batcher_test.go index 28e635b3298..8189ee43c6f 100644 --- a/util/batcher/batcher_test.go +++ b/util/batcher/batcher_test.go @@ -52,7 +52,7 @@ var _ = Describe("Batcher", func() { }) It("calls the callback", func() { - batcher.RequestByGUID([]string{"one", "two", "three"}, fakeCallback) + _, _ = batcher.RequestByGUID([]string{"one", "two", "three"}, fakeCallback) Expect(calls).To(HaveLen(1)) Expect(calls[0]).To(Equal([]string{"one", "two", "three"})) @@ -60,7 +60,7 @@ var _ = Describe("Batcher", func() { When("the guids list exceeds the batch size", func() { It("calls the callback multiple times", func() { - batcher.RequestByGUID(spreadGuids(0, 520), fakeCallback) + _, _ = batcher.RequestByGUID(spreadGuids(0, 520), fakeCallback) Expect(calls).To(HaveLen(3)) Expect(calls[0]).To(Equal(spreadGuids(0, 200))) diff --git a/util/railway/railway_test.go b/util/railway/railway_test.go index 8f17f36571f..e250b375fab 100644 --- a/util/railway/railway_test.go +++ b/util/railway/railway_test.go @@ -17,7 +17,7 @@ var _ = Describe("Sequentially()", func() { j = 0 ) - railway.Sequentially( + _, _ = railway.Sequentially( func() (ccv3.Warnings, error) { i = 1; return ccv3.Warnings{}, nil }, func() (ccv3.Warnings, error) { j = 2; return ccv3.Warnings{}, nil }, ) @@ -29,7 +29,7 @@ var _ = Describe("Sequentially()", func() { It("calls the tracks in order", func() { i := 0 - railway.Sequentially( + _, _ = railway.Sequentially( func() (ccv3.Warnings, error) { i = 1; return ccv3.Warnings{}, nil }, func() (ccv3.Warnings, error) { i = 2; return ccv3.Warnings{}, nil }, ) diff --git a/util/ui/ui.go b/util/ui/ui.go index 84b025cb447..fc362246ccd 100644 --- a/util/ui/ui.go +++ b/util/ui/ui.go @@ -276,18 +276,6 @@ func (ui *UI) DisplayTextWithFlavor(template string, templateValues ...map[strin fmt.Fprintf(ui.Out, "%s\n", ui.TranslateText(template, firstTemplateValues)) } -func getIndent(depth int, addHyphen bool) string { - if depth == 0 { - return "" - } - indent := strings.Repeat(" ", depth-1) - if addHyphen { - return indent + "- " - } else { - return indent + " " - } -} - // DisplayDiffAddition displays added lines in a diff, colored green and prefixed with '+' func (ui *UI) DisplayDiffAddition(lines string, depth int, addHyphen bool) { ui.terminalLock.Lock() @@ -509,3 +497,15 @@ func sum(intSlice []int) int { return sum } + +func getIndent(depth int, addHyphen bool) string { + if depth == 0 { + return "" + } + indent := strings.Repeat(" ", depth-1) + if addHyphen { + return indent + "- " + } else { + return indent + " " + } +} diff --git a/util/ui/ui_test.go b/util/ui/ui_test.go index 15c3dc06b45..9a496264aed 100644 --- a/util/ui/ui_test.go +++ b/util/ui/ui_test.go @@ -197,7 +197,7 @@ var _ = Describe("UI", func() { "arr": []string{"a", "b"}, } - ui.DisplayJSON("named_json", obj) + _ = ui.DisplayJSON("named_json", obj) Expect(out).To(SatisfyAll( Say("named_json: {\n"), From d2688d12284f5d8ff3f467dd7bac4c2aa46999e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 12:19:52 -0700 Subject: [PATCH 020/543] Bump rack from 2.2.3 to 2.2.3.1 in /fixtures/applications/example-app (#2278) Bumps [rack](https://github.com/rack/rack) from 2.2.3 to 2.2.3.1. - [Release notes](https://github.com/rack/rack/releases) - [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md) - [Commits](https://github.com/rack/rack/compare/2.2.3...2.2.3.1) --- updated-dependencies: - dependency-name: rack dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- fixtures/applications/example-app/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fixtures/applications/example-app/Gemfile.lock b/fixtures/applications/example-app/Gemfile.lock index 4c418edc0ba..873c69a8898 100644 --- a/fixtures/applications/example-app/Gemfile.lock +++ b/fixtures/applications/example-app/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: mustermann (1.1.1) ruby2_keywords (~> 0.0.1) - rack (2.2.3) + rack (2.2.3.1) rack-protection (2.2.0) rack ruby2_keywords (0.0.5) From 68d897a19792bdffe20c39589148216b23c950d1 Mon Sep 17 00:00:00 2001 From: Peter Levine Date: Tue, 23 Aug 2022 14:03:04 -0400 Subject: [PATCH 021/543] feat: Add move-route command to cf cli (#2302) * feat: Add transfer-route-owner command to cf cli Co-authored-by: Shwetha Gururaj * feat: update repository variable. Update workflow action * fix: remove windows 2016, swap to windows latest. Fix relative file path * chore: windows values * chore: swap to Rel instead of join * chore: main dir * chore: join on current dir * chore: clean path first * chore: try from slash * chore: remove dot syntax * chore: swap to move-route Co-authored-by: Shwetha Gururaj * chore: windows tests again * chore: remove test println * chore: update path to /relationships/space from transferowner Co-authored-by: Shwetha Gururaj * Change display text * Remove focus * Fix typo * Send non-array data in requestBody * Change message ordering * Create shared domain instead of private Co-authored-by: Shwetha Gururaj --- .github/workflows/code-quality.yml | 2 +- .github/workflows/units.yml | 9 +- actor/v7action/cloud_controller_client.go | 1 + actor/v7action/route.go | 5 + .../fake_cloud_controller_client.go | 80 +++++ .../handle_app_path_override_test.go | 7 +- .../ccv3/internal/api_routes.go | 2 + api/cloudcontroller/ccv3/route.go | 25 ++ command/common/command_list_v7.go | 1 + command/common/internal/help_all_display.go | 1 + command/v7/actor.go | 1 + command/v7/move_route_command.go | 101 +++++++ command/v7/move_route_command_test.go | 279 ++++++++++++++++++ command/v7/v7fakes/fake_actor.go | 80 +++++ .../v7/isolated/move_route_command_test.go | 204 +++++++++++++ 15 files changed, 789 insertions(+), 9 deletions(-) create mode 100644 command/v7/move_route_command.go create mode 100644 command/v7/move_route_command_test.go create mode 100644 integration/v7/isolated/move_route_command_test.go diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index e404f2dc45a..b37b1bef652 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -12,7 +12,7 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest - if: ${{ github.action_repository == 'cloudfoundry/cli' }} + if: ${{ github.repository == 'cloudfoundry/cli' }} permissions: actions: read contents: read diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index db2f329155e..55b534dc9c0 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -1,10 +1,6 @@ name: Units Tests -on: - push: - branches: - - "*" - +on: [push] permissions: contents: write @@ -83,9 +79,8 @@ jobs: strategy: matrix: os: - - windows-2022 + - windows-latest - windows-2019 - - windows-2016 runs-on: ${{ matrix.os }} needs: shared-values defaults: diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index 756c0528ffd..050b4d5fdac 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -140,6 +140,7 @@ type CloudControllerClient interface { GetUsers(query ...ccv3.Query) ([]resources.User, ccv3.Warnings, error) MakeRequestSendReceiveRaw(Method string, URL string, headers http.Header, requestBody []byte) ([]byte, *http.Response, error) MapRoute(routeGUID string, appGUID string, destinationProtocol string) (ccv3.Warnings, error) + MoveRoute(routeGUID string, spaceGUID string) (ccv3.Warnings, error) PollJob(jobURL ccv3.JobURL) (ccv3.Warnings, error) PollJobForState(jobURL ccv3.JobURL, state constant.JobState) (ccv3.Warnings, error) PollJobToEventStream(jobURL ccv3.JobURL) chan ccv3.PollJobEvent diff --git a/actor/v7action/route.go b/actor/v7action/route.go index 16f7bc9f4ce..7cac6180001 100644 --- a/actor/v7action/route.go +++ b/actor/v7action/route.go @@ -426,6 +426,11 @@ func (actor Actor) GetApplicationRoutes(appGUID string) ([]resources.Route, Warn return routes, allWarnings, nil } +func (actor Actor) MoveRoute(routeGUID string, spaceGUID string) (Warnings, error) { + warnings, err := actor.CloudControllerClient.MoveRoute(routeGUID, spaceGUID) + return Warnings(warnings), err +} + func getDomainName(fullURL, host, path string, port int) string { domainWithoutHost := strings.TrimPrefix(fullURL, host+".") domainWithoutPath := strings.TrimSuffix(domainWithoutHost, path) diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 15a378271c2..8b2d4240737 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -1966,6 +1966,20 @@ type FakeCloudControllerClient struct { result1 ccv3.Warnings result2 error } + MoveRouteStub func(string, string) (ccv3.Warnings, error) + moveRouteMutex sync.RWMutex + moveRouteArgsForCall []struct { + arg1 string + arg2 string + } + moveRouteReturns struct { + result1 ccv3.Warnings + result2 error + } + moveRouteReturnsOnCall map[int]struct { + result1 ccv3.Warnings + result2 error + } PollJobStub func(ccv3.JobURL) (ccv3.Warnings, error) pollJobMutex sync.RWMutex pollJobArgsForCall []struct { @@ -11192,6 +11206,70 @@ func (fake *FakeCloudControllerClient) MapRouteReturnsOnCall(i int, result1 ccv3 }{result1, result2} } +func (fake *FakeCloudControllerClient) MoveRoute(arg1 string, arg2 string) (ccv3.Warnings, error) { + fake.moveRouteMutex.Lock() + ret, specificReturn := fake.moveRouteReturnsOnCall[len(fake.moveRouteArgsForCall)] + fake.moveRouteArgsForCall = append(fake.moveRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("MoveRoute", []interface{}{arg1, arg2}) + fake.moveRouteMutex.Unlock() + if fake.MoveRouteStub != nil { + return fake.MoveRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.moveRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeCloudControllerClient) MoveRouteCallCount() int { + fake.moveRouteMutex.RLock() + defer fake.moveRouteMutex.RUnlock() + return len(fake.moveRouteArgsForCall) +} + +func (fake *FakeCloudControllerClient) MoveRouteCalls(stub func(string, string) (ccv3.Warnings, error)) { + fake.moveRouteMutex.Lock() + defer fake.moveRouteMutex.Unlock() + fake.MoveRouteStub = stub +} + +func (fake *FakeCloudControllerClient) MoveRouteArgsForCall(i int) (string, string) { + fake.moveRouteMutex.RLock() + defer fake.moveRouteMutex.RUnlock() + argsForCall := fake.moveRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCloudControllerClient) MoveRouteReturns(result1 ccv3.Warnings, result2 error) { + fake.moveRouteMutex.Lock() + defer fake.moveRouteMutex.Unlock() + fake.MoveRouteStub = nil + fake.moveRouteReturns = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeCloudControllerClient) MoveRouteReturnsOnCall(i int, result1 ccv3.Warnings, result2 error) { + fake.moveRouteMutex.Lock() + defer fake.moveRouteMutex.Unlock() + fake.MoveRouteStub = nil + if fake.moveRouteReturnsOnCall == nil { + fake.moveRouteReturnsOnCall = make(map[int]struct { + result1 ccv3.Warnings + result2 error + }) + } + fake.moveRouteReturnsOnCall[i] = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeCloudControllerClient) PollJob(arg1 ccv3.JobURL) (ccv3.Warnings, error) { fake.pollJobMutex.Lock() ret, specificReturn := fake.pollJobReturnsOnCall[len(fake.pollJobArgsForCall)] @@ -14721,6 +14799,8 @@ func (fake *FakeCloudControllerClient) Invocations() map[string][][]interface{} defer fake.makeRequestSendReceiveRawMutex.RUnlock() fake.mapRouteMutex.RLock() defer fake.mapRouteMutex.RUnlock() + fake.moveRouteMutex.RLock() + defer fake.moveRouteMutex.RUnlock() fake.pollJobMutex.RLock() defer fake.pollJobMutex.RUnlock() fake.pollJobForStateMutex.RLock() diff --git a/actor/v7pushaction/handle_app_path_override_test.go b/actor/v7pushaction/handle_app_path_override_test.go index 60376947d79..b77a6d521dd 100644 --- a/actor/v7pushaction/handle_app_path_override_test.go +++ b/actor/v7pushaction/handle_app_path_override_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" . "code.cloudfoundry.org/cli/actor/v7pushaction" "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" @@ -94,7 +95,11 @@ var _ = Describe("HandleAppPathOverride", func() { absoluteAppFilehandle, err = ioutil.TempFile("", "") Expect(err).NotTo(HaveOccurred()) defer absoluteAppFilehandle.Close() - relativeAppFilePath = filepath.Join(".", absoluteAppFilehandle.Name()) + if runtime.GOOS == "windows" { + relativeAppFilePath = absoluteAppFilehandle.Name() + } else { + relativeAppFilePath = filepath.Join(filepath.Dir("/"), absoluteAppFilehandle.Name()) + } flagOverrides.ProvidedAppPath = relativeAppFilePath // TODO: Do NOT use Chdir! it affects ALL other threads diff --git a/api/cloudcontroller/ccv3/internal/api_routes.go b/api/cloudcontroller/ccv3/internal/api_routes.go index 7029d8073fa..5da806e7d29 100644 --- a/api/cloudcontroller/ccv3/internal/api_routes.go +++ b/api/cloudcontroller/ccv3/internal/api_routes.go @@ -130,6 +130,7 @@ const ( PatchSpaceFeaturesRequest = "PatchSpaceFeatures" PatchSpaceQuotaRequest = "PatchSpaceQuota" PatchStackRequest = "PatchStack" + PatchMoveRouteRequest = "PatchMoveRouteRequest" PostApplicationActionApplyManifest = "PostApplicationActionApplyM" PostApplicationActionRestartRequest = "PostApplicationActionRestart" PostApplicationActionStartRequest = "PostApplicationActionStart" @@ -279,6 +280,7 @@ var APIRoutes = map[string]Route{ UnmapRouteRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodDelete}, PatchDestinationRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodPatch}, ShareRouteRequest: {Path: "/v3/routes/:route_guid/relationships/shared_spaces", Method: http.MethodPost}, + PatchMoveRouteRequest: {Path: "/v3/routes/:route_guid/relationships/space", Method: http.MethodPatch}, GetSecurityGroupsRequest: {Path: "/v3/security_groups", Method: http.MethodGet}, PostSecurityGroupRequest: {Path: "/v3/security_groups", Method: http.MethodPost}, DeleteSecurityGroupRequest: {Path: "/v3/security_groups/:security_group_guid", Method: http.MethodDelete}, diff --git a/api/cloudcontroller/ccv3/route.go b/api/cloudcontroller/ccv3/route.go index d7ad9a2c182..56f068640a6 100644 --- a/api/cloudcontroller/ccv3/route.go +++ b/api/cloudcontroller/ccv3/route.go @@ -173,3 +173,28 @@ func (client Client) ShareRoute(routeGUID string, spaceGUID string) (Warnings, e }) return warnings, err } + +func (client Client) MoveRoute(routeGUID string, spaceGUID string) (Warnings, error) { + type space struct { + GUID string `json:"guid"` + } + + type body struct { + Data space `json:"data"` + } + + requestBody := body{ + Data: space{ + GUID: spaceGUID, + }, + } + + var responseBody resources.Build + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.PatchMoveRouteRequest, + URIParams: internal.Params{"route_guid": routeGUID}, + RequestBody: &requestBody, + ResponseBody: &responseBody, + }) + return warnings, err +} diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index fada59bcb02..588e47e391f 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -173,6 +173,7 @@ type commandList struct { Target v7.TargetCommand `command:"target" alias:"t" description:"Set or view the targeted org or space"` Tasks v7.TasksCommand `command:"tasks" description:"List tasks of an app"` TerminateTask v7.TerminateTaskCommand `command:"terminate-task" description:"Terminate a running task of an app"` + MoveRoute v7.MoveRouteCommand `command:"move-route" description:"Assign a route to a different space"` UnbindRouteService v7.UnbindRouteServiceCommand `command:"unbind-route-service" alias:"urs" description:"Unbind a service instance from an HTTP route"` UnbindRunningSecurityGroup v7.UnbindRunningSecurityGroupCommand `command:"unbind-running-security-group" description:"Unbind a security group from the set of security groups for running applications globally"` UnbindSecurityGroup v7.UnbindSecurityGroupCommand `command:"unbind-security-group" description:"Unbind a security group from a space"` diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 8c6e9957f09..97990a5002d 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -69,6 +69,7 @@ var HelpCategoryList = []HelpCategory{ {"delete-orphaned-routes"}, {"update-destination"}, {"share-route"}, + {"move-route"}, }, }, { diff --git a/command/v7/actor.go b/command/v7/actor.go index 64ef63dd1f0..3bbae507c69 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -183,6 +183,7 @@ type Actor interface { MakeCurlRequest(httpMethod string, path string, customHeaders []string, httpData string, failOnHTTPError bool) ([]byte, *http.Response, error) MapRoute(routeGUID string, appGUID string, destinationProtocol string) (v7action.Warnings, error) Marketplace(filter v7action.MarketplaceFilter) ([]v7action.ServiceOfferingWithPlans, v7action.Warnings, error) + MoveRoute(routeGUID string, spaceGUID string) (v7action.Warnings, error) ParseAccessToken(accessToken string) (jwt.JWT, error) PollBuild(buildGUID string, appName string) (resources.Droplet, v7action.Warnings, error) PollPackage(pkg resources.Package) (resources.Package, v7action.Warnings, error) diff --git a/command/v7/move_route_command.go b/command/v7/move_route_command.go new file mode 100644 index 00000000000..4ba150568d1 --- /dev/null +++ b/command/v7/move_route_command.go @@ -0,0 +1,101 @@ +package v7 + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/command/flag" +) + +type MoveRouteCommand struct { + BaseCommand + + RequireArgs flag.Domain `positional-args:"yes"` + Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` + Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` + DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` + DestinationSpace string `short:"s" description:"The space of the destination app (Default: targeted space)"` + + relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` +} + +func (cmd MoveRouteCommand) Usage() string { + return ` + Transfers the ownership of a route to a another space: + CF_NAME move-route DOMAIN [--hostname HOSTNAME] [--path PATH] -s OTHER_SPACE [-o OTHER_ORG]` +} + +func (cmd MoveRouteCommand) Examples() string { + return ` + CF_NAME move-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo + CF_NAME move-route example.com --hostname myHost -s TargetSpace # myhost.example.com + CF_NAME move-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com` +} + +func (cmd MoveRouteCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + domain, warnings, err := cmd.Actor.GetDomainByName(cmd.RequireArgs.Domain) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + path := cmd.Path.Path + route, warnings, err := cmd.Actor.GetRouteByAttributes(domain, cmd.Hostname, path, 0) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + if _, ok := err.(actionerror.RouteNotFoundError); ok { + cmd.UI.DisplayText("Can not transfer ownership of route:") + return err + } + } + + destinationOrgName := cmd.DestinationOrg + + if destinationOrgName == "" { + destinationOrgName = cmd.Config.TargetedOrganizationName() + } + + destinationOrg, warnings, err := cmd.Actor.GetOrganizationByName(destinationOrgName) + + if err != nil { + if _, ok := err.(actionerror.OrganizationNotFoundError); ok { + cmd.UI.DisplayText("Can not transfer ownership of route:") + return err + } + } + + targetedSpace, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.DestinationSpace, destinationOrg.GUID) + if err != nil { + if _, ok := err.(actionerror.SpaceNotFoundError); ok { + cmd.UI.DisplayText("Can not transfer ownership of route:") + return err + } + } + + url := desiredURL(domain.Name, cmd.Hostname, path, 0) + cmd.UI.DisplayTextWithFlavor("Move ownership of route {{.URL}} to space {{.DestinationSpace}} as {{.User}}", + map[string]interface{}{ + "URL": url, + "DestinationSpace": cmd.DestinationSpace, + "User": user.Name, + }) + warnings, err = cmd.Actor.MoveRoute( + route.GUID, + targetedSpace.GUID, + ) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + cmd.UI.DisplayOK() + + return nil +} diff --git a/command/v7/move_route_command_test.go b/command/v7/move_route_command_test.go new file mode 100644 index 00000000000..4ff7b7360bd --- /dev/null +++ b/command/v7/move_route_command_test.go @@ -0,0 +1,279 @@ +package v7_test + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/cf/errors" + "code.cloudfoundry.org/cli/command/commandfakes" + "code.cloudfoundry.org/cli/command/flag" + v7 "code.cloudfoundry.org/cli/command/v7" + "code.cloudfoundry.org/cli/command/v7/v7fakes" + "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/util/configv3" + "code.cloudfoundry.org/cli/util/ui" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("move-route Command", func() { + var ( + cmd v7.MoveRouteCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + binaryName string + executeErr error + domainName string + orgName string + spaceName string + hostname string + path string + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + binaryName = "myBinaryBread" + fakeConfig.BinaryNameReturns(binaryName) + + domainName = "some-domain.com" + orgName = "org-name-a" + spaceName = "space-name-a" + hostname = "myHostname" + path = "myPath" + + cmd = v7.MoveRouteCommand{ + BaseCommand: v7.BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + RequireArgs: flag.Domain{Domain: domainName}, + Hostname: hostname, + Path: flag.V7RoutePath{Path: path}, + DestinationOrg: orgName, + DestinationSpace: spaceName, + } + + fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space", GUID: "some-space-guid"}) + fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org"}) + fakeConfig.CurrentUserReturns(configv3.User{Name: "some-user"}, nil) + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + It("checks that target", func() { + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + + When("checking target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) + }) + It("returns an error", func() { + Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) + + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("the user is not logged in", func() { + var expectedErr error + + BeforeEach(func() { + expectedErr = errors.New("some current user error") + fakeActor.GetCurrentUserReturns(configv3.User{}, expectedErr) + }) + + It("return an error", func() { + Expect(executeErr).To(Equal(expectedErr)) + }) + }) + + When("the user is logged in and targeted", func() { + When("getting the domain errors", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns(resources.Domain{}, v7action.Warnings{"get-domain-warnings"}, errors.New("get-domain-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(executeErr).To(MatchError(errors.New("get-domain-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(0)) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(0)) + + Expect(fakeActor.MoveRouteCallCount()).To(Equal(0)) + }) + }) + + When("getting the domain succeeds", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns( + resources.Domain{Name: domainName, GUID: "domain-guid"}, + v7action.Warnings{"get-domain-warnings"}, + nil, + ) + }) + + When("the requested route does not exist", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.RouteNotFoundError{}, + ) + }) + + It("displays error message", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-route-warnings")) + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + }) + }) + + When("the requested route exists", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{GUID: "route-guid"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + When("getting the target space errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.SpaceNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetOrganizationByNameArgsForCall(0)).To(Equal(orgName)) + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + + Expect(fakeActor.MoveRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target org errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.OrganizationNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.MoveRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target space succeeds", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "space-guid-b"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + It("exits 0 with helpful message that the route is now transferred", func() { + Expect(executeErr).ShouldNot(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + Expect(fakeActor.MoveRouteCallCount()).To(Equal(1)) + }) + }) + }) + }) + }) +}) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 1a950e4692e..f3cddabd4f7 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -2489,6 +2489,20 @@ type FakeActor struct { result2 v7action.Warnings result3 error } + MoveRouteStub func(string, string) (v7action.Warnings, error) + moveRouteMutex sync.RWMutex + moveRouteArgsForCall []struct { + arg1 string + arg2 string + } + moveRouteReturns struct { + result1 v7action.Warnings + result2 error + } + moveRouteReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } ParseAccessTokenStub func(string) (jwt.JWT, error) parseAccessTokenMutex sync.RWMutex parseAccessTokenArgsForCall []struct { @@ -14180,6 +14194,70 @@ func (fake *FakeActor) MarketplaceReturnsOnCall(i int, result1 []v7action.Servic }{result1, result2, result3} } +func (fake *FakeActor) MoveRoute(arg1 string, arg2 string) (v7action.Warnings, error) { + fake.moveRouteMutex.Lock() + ret, specificReturn := fake.moveRouteReturnsOnCall[len(fake.moveRouteArgsForCall)] + fake.moveRouteArgsForCall = append(fake.moveRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("MoveRoute", []interface{}{arg1, arg2}) + fake.moveRouteMutex.Unlock() + if fake.MoveRouteStub != nil { + return fake.MoveRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.moveRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) MoveRouteCallCount() int { + fake.moveRouteMutex.RLock() + defer fake.moveRouteMutex.RUnlock() + return len(fake.moveRouteArgsForCall) +} + +func (fake *FakeActor) MoveRouteCalls(stub func(string, string) (v7action.Warnings, error)) { + fake.moveRouteMutex.Lock() + defer fake.moveRouteMutex.Unlock() + fake.MoveRouteStub = stub +} + +func (fake *FakeActor) MoveRouteArgsForCall(i int) (string, string) { + fake.moveRouteMutex.RLock() + defer fake.moveRouteMutex.RUnlock() + argsForCall := fake.moveRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeActor) MoveRouteReturns(result1 v7action.Warnings, result2 error) { + fake.moveRouteMutex.Lock() + defer fake.moveRouteMutex.Unlock() + fake.MoveRouteStub = nil + fake.moveRouteReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) MoveRouteReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.moveRouteMutex.Lock() + defer fake.moveRouteMutex.Unlock() + fake.MoveRouteStub = nil + if fake.moveRouteReturnsOnCall == nil { + fake.moveRouteReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.moveRouteReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) ParseAccessToken(arg1 string) (jwt.JWT, error) { fake.parseAccessTokenMutex.Lock() ret, specificReturn := fake.parseAccessTokenReturnsOnCall[len(fake.parseAccessTokenArgsForCall)] @@ -19326,6 +19404,8 @@ func (fake *FakeActor) Invocations() map[string][][]interface{} { defer fake.mapRouteMutex.RUnlock() fake.marketplaceMutex.RLock() defer fake.marketplaceMutex.RUnlock() + fake.moveRouteMutex.RLock() + defer fake.moveRouteMutex.RUnlock() fake.parseAccessTokenMutex.RLock() defer fake.parseAccessTokenMutex.RUnlock() fake.pollBuildMutex.RLock() diff --git a/integration/v7/isolated/move_route_command_test.go b/integration/v7/isolated/move_route_command_test.go new file mode 100644 index 00000000000..b973fb1d007 --- /dev/null +++ b/integration/v7/isolated/move_route_command_test.go @@ -0,0 +1,204 @@ +package isolated + +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" + . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("move route command", func() { + Context("Help", func() { + It("appears in cf help -a", func() { + session := helpers.CF("help", "-a") + + Eventually(session).Should(Exit(0)) + Expect(session).To(HaveCommandInCategoryWithDescription("move-route", "ROUTES", "Assign a route to a different space")) + }) + + It("displays the help information", func() { + session := helpers.CF("move-route", "--help") + Eventually(session).Should(Say(`NAME:`)) + Eventually(session).Should(Say("move-route - Assign a route to a different space")) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`USAGE:`)) + Eventually(session).Should(Say(`Transfers the ownership of a route to a another space:`)) + Eventually(session).Should(Say(`cf move-route DOMAIN \[--hostname HOSTNAME\] \[--path PATH\] -s OTHER_SPACE \[-o OTHER_ORG\]`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`EXAMPLES:`)) + Eventually(session).Should(Say(`cf move-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo`)) + Eventually(session).Should(Say(`cf move-route example.com --hostname myHost -s TargetSpace # myhost.example.com`)) + Eventually(session).Should(Say(`cf move-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`OPTIONS:`)) + Eventually(session).Should(Say(`--hostname, -n\s+Hostname for the HTTP route \(required for shared domains\)`)) + Eventually(session).Should(Say(`--path\s+Path for the HTTP route`)) + Eventually(session).Should(Say(`-o\s+The org of the destination app \(Default: targeted org\)`)) + Eventually(session).Should(Say(`-s\s+The space of the destination app \(Default: targeted space\)`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`SEE ALSO:`)) + Eventually(session).Should(Say(`create-route, map-route, routes, unmap-route`)) + + Eventually(session).Should(Exit(0)) + }) + }) + + When("the environment is not setup correctly", func() { + It("fails with the appropriate errors", func() { + helpers.CheckEnvironmentTargetedCorrectly(true, false, ReadOnlyOrg, "move-route", "some-domain", "-s SOME_SPACE") + }) + }) + + When("the environment is set up correctly", func() { + var ( + userName string + orgName string + spaceName string + ) + + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionHTTP2RoutingV3) + orgName = helpers.NewOrgName() + spaceName = helpers.NewSpaceName() + + helpers.SetupCF(orgName, spaceName) + userName, _ = helpers.GetCredentials() + }) + + AfterEach(func() { + helpers.QuickDeleteOrg(orgName) + }) + + When("the domain exists", func() { + var ( + domainName string + targetSpaceName string + ) + + BeforeEach(func() { + domainName = helpers.NewDomainName() + }) + + When("the route exists", func() { + var ( + domain helpers.Domain + hostname string + ) + When("the target space exists in targeted org", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.CreateShared() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.DeleteShared() + }) + + It("transfers the route to the destination space", func() { + session := helpers.CF("move-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say(`Move ownership of route %s.%s to space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the target organization does not exist", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + targetOrgName = helpers.NewOrgName() + domain.CreateShared() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 and an error message", func() { + session := helpers.CF("move-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not transfer ownership of route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + + When("the target space exists in another existing org", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + targetOrgName = helpers.NewOrgName() + targetSpaceName = helpers.NewSpaceName() + helpers.CreateOrgAndSpace(targetOrgName, targetSpaceName) + helpers.SetupCF(orgName, spaceName) + domain.CreateShared() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.DeleteShared() + }) + + It("Transfers ownership of the route to the destination space", func() { + session := helpers.CF("move-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say(`Move ownership of route %s.%s to space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the space does not exist", func() { + var destinationSpaceName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + destinationSpaceName = "doesNotExistSpace" + domain.CreateShared() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 with an error", func() { + session := helpers.CF("move-route", domainName, "--hostname", hostname, "-s", destinationSpaceName) + Eventually(session).Should(Say("Can not transfer ownership of route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + + When("the route does not exist", func() { + var ( + domain helpers.Domain + hostname string + ) + + When("the target space exists", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.CreateShared() + }) + + It("exits with 1 with an error message", func() { + session := helpers.CF("move-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not transfer ownership of route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + }) + }) +}) From 68b9ce2e02808add0af3139c8294f978a71eec53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Diego=20Gonz=C3=A1lez?= <25335192+jdgonzaleza@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:14:45 -0500 Subject: [PATCH 022/543] Adds regex in order to get cf_users number (#2310) --- bin/cleanup-integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cleanup-integration b/bin/cleanup-integration index bb5592b173d..3dd18f8c022 100755 --- a/bin/cleanup-integration +++ b/bin/cleanup-integration @@ -62,7 +62,7 @@ for stack in $(cf stacks | awk '/INTEGRATION-STACK/ { print $1 }'); do cf curl -X DELETE "/v3/stacks/$(cf stack --guid $stack)" done -CF_USERS=$(cf curl /v3/users | grep total_results | grep -o '[0-9]\+') +CF_USERS=$(cf curl /v3/users | grep -oP '(?<="total_results":)[^"]*' |grep -o '[0-9]\+') USER_PAGES=$(( $CF_USERS / 50 + 1)) for ((i=1; i<=${USER_PAGES}; i++)) ; do From bfbbcb5d07d9e5c752511c400fe0651b7c8c288c Mon Sep 17 00:00:00 2001 From: Julian Hjortshoj Date: Thu, 18 Aug 2022 15:49:12 -0700 Subject: [PATCH 023/543] Add support for Korifi to the `auth` command (#2296) --- command/v7/auth_command.go | 26 ++++++++++++- command/v7/auth_command_test.go | 65 ++++++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/command/v7/auth_command.go b/command/v7/auth_command.go index 0b03acd0a1e..6b787b3245f 100644 --- a/command/v7/auth_command.go +++ b/command/v7/auth_command.go @@ -1,6 +1,7 @@ package v7 import ( + "errors" "fmt" "code.cloudfoundry.org/cli/api/uaa/constant" @@ -77,6 +78,29 @@ func (cmd AuthCommand) Execute(args []string) error { grantType = constant.GrantTypeClientCredentials credentials["client_id"] = username credentials["client_secret"] = password + } else if cmd.Config.IsCFOnK8s() { + prompts, err := cmd.Actor.GetLoginPrompts() + if err != nil { + return err + } + prompt, ok := prompts["k8s-auth-info"] + if !ok { + return errors.New("kubernetes login context is missing") + } + + userFound := false + for _, val := range prompt.Entries { + if val == username { + userFound = true + break + } + } + if !userFound { + return errors.New("kubernetes user not found in configuration: " + username) + } + credentials = map[string]string{ + "k8s-auth-info": username, + } } else { credentials = map[string]string{ "username": username, @@ -118,7 +142,7 @@ func (cmd AuthCommand) getUsernamePassword() (string, string, error) { if password == "" { if envPassword := cmd.Config.CFPassword(); envPassword != "" { password = envPassword - } else { + } else if !cmd.Config.IsCFOnK8s() { passwordMissing = true } } diff --git a/command/v7/auth_command_test.go b/command/v7/auth_command_test.go index 152c648a3c9..682aa965bca 100644 --- a/command/v7/auth_command_test.go +++ b/command/v7/auth_command_test.go @@ -1,6 +1,7 @@ package v7_test import ( + "code.cloudfoundry.org/cli/cf/configuration/coreconfig" "errors" "code.cloudfoundry.org/cli/api/uaa" @@ -18,12 +19,13 @@ import ( var _ = Describe("auth Command", func() { var ( - cmd AuthCommand - testUI *ui.UI - fakeActor *v7fakes.FakeActor - fakeConfig *commandfakes.FakeConfig - binaryName string - err error + cmd AuthCommand + testUI *ui.UI + fakeActor *v7fakes.FakeActor + fakeConfig *commandfakes.FakeConfig + binaryName string + err error + k8sLoginPrompts map[string]coreconfig.AuthPrompt ) BeforeEach(func() { @@ -43,6 +45,11 @@ var _ = Describe("auth Command", func() { fakeConfig.BinaryNameReturns(binaryName) fakeConfig.UAAOAuthClientReturns("cf") fakeConfig.APIVersionReturns("3.99.0") + k8sLoginPrompts = map[string]coreconfig.AuthPrompt{ + "k8s-auth-info": { + Entries: []string{"myuser"}, + }, + } }) JustBeforeEach(func() { @@ -113,6 +120,17 @@ var _ = Describe("auth Command", func() { MissingPassword: true, })) }) + + When("authenticating against Korifi", func() { + BeforeEach(func() { + fakeConfig.IsCFOnK8sReturns(true) + fakeActor.GetLoginPromptsReturns(k8sLoginPrompts, nil) + }) + + It("succeeds", func() { + Expect(err).NotTo(HaveOccurred()) + }) + }) }) }) @@ -298,6 +316,41 @@ var _ = Describe("auth Command", func() { }) }) }) + + When("authenticating against Korifi", func() { + BeforeEach(func() { + cmd.RequiredArgs.Username = "myuser" + fakeConfig.IsCFOnK8sReturns(true) + fakeActor.GetLoginPromptsReturns(k8sLoginPrompts, nil) + }) + + When("the specified username doesn't match any k8s context", func() { + BeforeEach(func() { + cmd.RequiredArgs.Username = "some-unknown-user" + }) + + It("errors", func() { + Expect(err).To(MatchError(errors.New("kubernetes user not found in configuration: some-unknown-user"))) + }) + }) + + When("the prompts don't contain k8s authentication information", func() { + BeforeEach(func() { + k8sLoginPrompts = map[string]coreconfig.AuthPrompt{ + "some-other-auth-info": { + Entries: []string{"myuser"}, + }, + } + fakeActor.GetLoginPromptsReturns(k8sLoginPrompts, nil) + }) + + It("errors", func() { + Expect(err).To(MatchError(errors.New("kubernetes login context is missing"))) + }) + + }) + + }) }) When("a user has manually added their client credentials to the config file", func() { From 4c59ec69e43a0b759c3916df795e31a3c81b612c Mon Sep 17 00:00:00 2001 From: Ryker Reed Date: Fri, 2 Sep 2022 12:51:17 -0400 Subject: [PATCH 024/543] Rename unacceptable var names. --- cf/ssh/ssh_test.go | 26 +++++++++++++------------- util/clissh/ssh_test.go | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cf/ssh/ssh_test.go b/cf/ssh/ssh_test.go index 8680011eaef..76b23f26f78 100644 --- a/cf/ssh/ssh_test.go +++ b/cf/ssh/ssh_test.go @@ -381,25 +381,25 @@ var _ = Describe("SSH", func() { }) Context("when stdin is a terminal", func() { - var master, slave *os.File + var pseudoterminal, terminal *os.File BeforeEach(func() { _, stdout, stderr := terminalHelper.StdStreams() var err error - master, slave, err = pty.Open() + pseudoterminal, terminal, err = pty.Open() Expect(err).NotTo(HaveOccurred()) fakeTerminalHelper.IsTerminalStub = terminalHelper.IsTerminal fakeTerminalHelper.GetFdInfoStub = terminalHelper.GetFdInfo fakeTerminalHelper.GetWinsizeStub = terminalHelper.GetWinsize - fakeTerminalHelper.StdStreamsReturns(slave, stdout, stderr) + fakeTerminalHelper.StdStreamsReturns(terminal, stdout, stderr) terminalHelper = fakeTerminalHelper }) AfterEach(func() { - master.Close() - // slave.Close() // race + pseudoterminal.Close() + // terminal.Close() // race }) Context("when a command is not specified", func() { @@ -733,23 +733,23 @@ var _ = Describe("SSH", func() { }) Context("when stdout is a terminal and a window size change occurs", func() { - var master, slave *os.File + var pseudoterminal, terminal *os.File BeforeEach(func() { stdin, _, stderr := terminalHelper.StdStreams() var err error - master, slave, err = pty.Open() + pseudoterminal, terminal, err = pty.Open() Expect(err).NotTo(HaveOccurred()) fakeTerminalHelper.IsTerminalStub = terminalHelper.IsTerminal fakeTerminalHelper.GetFdInfoStub = terminalHelper.GetFdInfo fakeTerminalHelper.GetWinsizeStub = terminalHelper.GetWinsize - fakeTerminalHelper.StdStreamsReturns(stdin, slave, stderr) + fakeTerminalHelper.StdStreamsReturns(stdin, terminal, stderr) terminalHelper = fakeTerminalHelper winsize := &term.Winsize{Height: 100, Width: 100} - err = term.SetWinsize(slave.Fd(), winsize) + err = term.SetWinsize(terminal.Fd(), winsize) Expect(err).NotTo(HaveOccurred()) fakeSecureSession.WaitStub = func() error { @@ -759,12 +759,12 @@ var _ = Describe("SSH", func() { // No dimension change for i := 0; i < 3; i++ { winsize := &term.Winsize{Height: 100, Width: 100} - err = term.SetWinsize(slave.Fd(), winsize) + err = term.SetWinsize(terminal.Fd(), winsize) Expect(err).NotTo(HaveOccurred()) } winsize := &term.Winsize{Height: 100, Width: 200} - err = term.SetWinsize(slave.Fd(), winsize) + err = term.SetWinsize(terminal.Fd(), winsize) Expect(err).NotTo(HaveOccurred()) err = syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) @@ -776,8 +776,8 @@ var _ = Describe("SSH", func() { }) AfterEach(func() { - master.Close() - slave.Close() + pseudoterminal.Close() + terminal.Close() }) It("sends window change events when the window dimensions change", func() { diff --git a/util/clissh/ssh_test.go b/util/clissh/ssh_test.go index c2cd5ecbffd..783465b9178 100644 --- a/util/clissh/ssh_test.go +++ b/util/clissh/ssh_test.go @@ -381,11 +381,11 @@ var _ = Describe("CLI SSH", func() { }) When("stdin is a terminal", func() { - var master *os.File + var terminal *os.File BeforeEach(func() { var err error - master, _, err = pty.Open() + terminal, _, err = pty.Open() Expect(err).NotTo(HaveOccurred()) terminalRequest = RequestTTYForce @@ -396,7 +396,7 @@ var _ = Describe("CLI SSH", func() { }) AfterEach(func() { - master.Close() + terminal.Close() }) When("a command is not specified", func() { @@ -716,20 +716,20 @@ var _ = Describe("CLI SSH", func() { }) When("stdout is a terminal and a window size change occurs", func() { - var master, slave *os.File + var pseudoterminal, terminal *os.File BeforeEach(func() { var err error - master, slave, err = pty.Open() + pseudoterminal, terminal, err = pty.Open() Expect(err).NotTo(HaveOccurred()) terminalHelper := DefaultTerminalHelper() fakeTerminalHelper.GetFdInfoStub = terminalHelper.GetFdInfo fakeTerminalHelper.GetWinsizeStub = terminalHelper.GetWinsize - fakeTerminalHelper.StdStreamsReturns(stdin, slave, stderr) + fakeTerminalHelper.StdStreamsReturns(stdin, terminal, stderr) winsize := &term.Winsize{Height: 100, Width: 100} - err = term.SetWinsize(slave.Fd(), winsize) + err = term.SetWinsize(terminal.Fd(), winsize) Expect(err).NotTo(HaveOccurred()) fakeSecureSession.WaitStub = func() error { @@ -739,12 +739,12 @@ var _ = Describe("CLI SSH", func() { // No dimension change for i := 0; i < 3; i++ { winsize := &term.Winsize{Height: 100, Width: 100} - err = term.SetWinsize(slave.Fd(), winsize) + err = term.SetWinsize(terminal.Fd(), winsize) Expect(err).NotTo(HaveOccurred()) } winsize := &term.Winsize{Height: 100, Width: 200} - err = term.SetWinsize(slave.Fd(), winsize) + err = term.SetWinsize(terminal.Fd(), winsize) Expect(err).NotTo(HaveOccurred()) err = syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) @@ -756,8 +756,8 @@ var _ = Describe("CLI SSH", func() { }) AfterEach(func() { - master.Close() - slave.Close() + pseudoterminal.Close() + terminal.Close() }) It("sends window change events when the window dimensions change", func() { From 19e480d8003b2e7c6ec96ac1fa5d4be4c71308ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Diego=20Gonz=C3=A1lez?= <25335192+jdgonzaleza@users.noreply.github.com> Date: Tue, 6 Sep 2022 15:00:25 -0500 Subject: [PATCH 025/543] Integration fixes * Update JSON formatting in test samples Recently CAPI removed JSON pretty formatting in cloudfoundry/cloud_controller_ng#2944 In this commit we enhanced the way of checking for values in json * Adds helpers for testing partial json Co-authored-by: Juan Diego Gonzalez Co-authored-by: Alexander Berez --- integration/helpers/set_env_command.go | 17 +++++++++++++++++ integration/helpers/ssh_command.go | 15 +++++++++++++++ .../v7/isolated/disable_ssh_command_test.go | 13 +++++++++++-- .../v7/isolated/enable_ssh_command_test.go | 12 ++++++++++-- .../v7/isolated/marketplace_command_test.go | 2 +- integration/v7/isolated/set_env_command_test.go | 17 ++++++++++++++--- 6 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 integration/helpers/set_env_command.go create mode 100644 integration/helpers/ssh_command.go diff --git a/integration/helpers/set_env_command.go b/integration/helpers/set_env_command.go new file mode 100644 index 00000000000..23c624cb220 --- /dev/null +++ b/integration/helpers/set_env_command.go @@ -0,0 +1,17 @@ +package helpers + +import ( + "encoding/json" +) + +func GetsDefaultEnvVarValue(stream []byte) string { + envVariableJSON := struct { + Var struct { + SomeEnvVar string `json:"SOME_ENV_VAR"` + } `json:"var"` + }{} + + json.Unmarshal(stream, &envVariableJSON) + + return envVariableJSON.Var.SomeEnvVar +} diff --git a/integration/helpers/ssh_command.go b/integration/helpers/ssh_command.go new file mode 100644 index 00000000000..df9bda85d90 --- /dev/null +++ b/integration/helpers/ssh_command.go @@ -0,0 +1,15 @@ +package helpers + +import ( + "encoding/json" +) + +func GetsEnablementValue(stream []byte) bool { + enablementResponse := struct { + Enabled bool `json:"enabled"` + }{} + + json.Unmarshal(stream, &enablementResponse) + + return enablementResponse.Enabled +} diff --git a/integration/v7/isolated/disable_ssh_command_test.go b/integration/v7/isolated/disable_ssh_command_test.go index ba5563fa693..d68dc0c58da 100644 --- a/integration/v7/isolated/disable_ssh_command_test.go +++ b/integration/v7/isolated/disable_ssh_command_test.go @@ -104,8 +104,13 @@ var _ = Describe("disable-ssh command", func() { Eventually(session).Should(Exit(0)) session = helpers.CF("curl", fmt.Sprintf("v3/apps/%s/ssh_enabled", helpers.AppGUID(appName))) - Eventually(session).Should(Say(`"enabled": %s`, "false")) + Eventually(session).Should(Exit(0)) + + bytes := session.Out.Contents() + + actualEnablementValue := helpers.GetsEnablementValue(bytes) + Expect(actualEnablementValue).To(Equal(false)) }) }) @@ -123,8 +128,12 @@ var _ = Describe("disable-ssh command", func() { Eventually(session).Should(Say("OK")) session = helpers.CF("curl", fmt.Sprintf("v3/apps/%s/ssh_enabled", helpers.AppGUID(appName))) - Eventually(session).Should(Say(`"enabled": %s`, "false")) Eventually(session).Should(Exit(0)) + + bytes := session.Out.Contents() + + actualEnablementValue := helpers.GetsEnablementValue(bytes) + Expect(actualEnablementValue).To(Equal(false)) }) }) }) diff --git a/integration/v7/isolated/enable_ssh_command_test.go b/integration/v7/isolated/enable_ssh_command_test.go index cfaa89bb3dc..460bc6fbf9c 100644 --- a/integration/v7/isolated/enable_ssh_command_test.go +++ b/integration/v7/isolated/enable_ssh_command_test.go @@ -104,8 +104,12 @@ var _ = Describe("enable-ssh command", func() { Eventually(session).Should(Exit(0)) session = helpers.CF("curl", fmt.Sprintf("v3/apps/%s/ssh_enabled", helpers.AppGUID(appName))) - Eventually(session).Should(Say(`"enabled": %s`, "true")) Eventually(session).Should(Exit(0)) + + bytes := session.Out.Contents() + + actualEnablementValue := helpers.GetsEnablementValue(bytes) + Expect(actualEnablementValue).To(Equal(true)) }) }) @@ -123,8 +127,12 @@ var _ = Describe("enable-ssh command", func() { Eventually(session).Should(Say("OK")) session = helpers.CF("curl", fmt.Sprintf("v3/apps/%s/ssh_enabled", helpers.AppGUID(appName))) - Eventually(session).Should(Say(`"enabled": %s`, "true")) Eventually(session).Should(Exit(0)) + + bytes := session.Out.Contents() + + actualEnablementValue := helpers.GetsEnablementValue(bytes) + Expect(actualEnablementValue).To(Equal(true)) }) }) diff --git a/integration/v7/isolated/marketplace_command_test.go b/integration/v7/isolated/marketplace_command_test.go index 288a59e9f2f..86ae14f4412 100644 --- a/integration/v7/isolated/marketplace_command_test.go +++ b/integration/v7/isolated/marketplace_command_test.go @@ -140,7 +140,7 @@ var _ = Describe("marketplace command", func() { Unit: "MONTHLY", }, { - Amount: map[string]float64{"usd": 0.999}, + Amount: map[string]float64{"usd": 1.00}, Unit: "1GB of messages over 20GB", }, } diff --git a/integration/v7/isolated/set_env_command_test.go b/integration/v7/isolated/set_env_command_test.go index fd5afe6a35c..2f2c29d1fca 100644 --- a/integration/v7/isolated/set_env_command_test.go +++ b/integration/v7/isolated/set_env_command_test.go @@ -130,8 +130,12 @@ var _ = Describe("set-env command", func() { Eventually(session).Should(Exit(0)) session = helpers.CF("curl", fmt.Sprintf("v3/apps/%s/environment_variables", helpers.AppGUID(appName))) - Eventually(session).Should(Say(`"%s": "%s"`, envVarName, envVarValue)) Eventually(session).Should(Exit(0)) + + bytes := session.Out.Contents() + + actualEnvVarValue := helpers.GetsDefaultEnvVarValue(bytes) + Expect(actualEnvVarValue).To(Equal(envVarValue)) }) // This is to prevent the '-' being read in as another flag @@ -149,8 +153,12 @@ var _ = Describe("set-env command", func() { Eventually(session).Should(Exit(0)) session = helpers.CF("curl", fmt.Sprintf("v3/apps/%s/environment_variables", helpers.AppGUID(appName))) - Eventually(session).Should(Say(`"%s": "%s"`, envVarName, envVarValue)) Eventually(session).Should(Exit(0)) + + bytes := session.Out.Contents() + + actualEnvVarValue := helpers.GetsDefaultEnvVarValue(bytes) + Expect(actualEnvVarValue).To(Equal(envVarValue)) }) }) }) @@ -170,8 +178,11 @@ var _ = Describe("set-env command", func() { Eventually(session).Should(Exit(0)) session = helpers.CF("curl", fmt.Sprintf("v3/apps/%s/environment_variables", helpers.AppGUID(appName))) - Eventually(session).Should(Say(`"%s": "%s"`, envVarName, someOtherValue)) Eventually(session).Should(Exit(0)) + bytes := session.Out.Contents() + + actualEnvVarValue := helpers.GetsDefaultEnvVarValue(bytes) + Expect(actualEnvVarValue).To(Equal(someOtherValue)) }) }) From cfcea0f30e0e5dd2a628f59a7f98def06ddf75c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Diego=20Gonz=C3=A1lez?= <25335192+jdgonzaleza@users.noreply.github.com> Date: Thu, 8 Sep 2022 12:55:09 -0500 Subject: [PATCH 026/543] logging rate limits flag introduction (#2313) * Feat: create and update org quotas with log volume (#2298) * Show org and space quotas (#2299) * org-quota and space-quota now show log volume * code currently assumes responses with log limits. Consider backward compatibility before shipping. * add log volume to space-quotas and org-quotas commands * will show "unlimited" for old CC API's which is technically correct * Fix unit tests in ccv3 * Create and update space quotas (#2300) * create-space-quota accepts log volume flag * update-space-quota accepts log volume flag * Update create-space-quota flag description * Make it clear that the log volume measured is in bytes rather than log lines. * Push and scale app and run-task * Bump rack from 2.2.3 to 2.2.3.1 in /fixtures/applications/example-app * `cf push` command accepts log rate limit flag * since `cf push` uses manifests this commit also adds support for log rate limit in manifests * `cf scale` command accepts log rate limit flag * app summary displayer can now handle rendering log rate and log rate limit metrics for running instances * app summaries will show `0 of 0` if the cloud controller does not support log rate limit container metrics * `cf run-task` command accepts log rate limit flag * update help text to suggest using ='s with -1 to avoid flag parsing problem * Flag parser accepts 0B, -1B, 0T, etc for flags * this affects non-manifest based commands * Extract constants for instance stats columns * Extract constant for column count Co-authored-by: Carson Long Co-authored-by: Matthew Kocher Co-authored-by: Rebecca Roberts Co-authored-by: Benjamin Fuller --- actor/v7action/organization_quota.go | 3 + actor/v7action/organization_quota_test.go | 16 +- actor/v7action/space_quota.go | 2 + actor/v7action/space_quota_test.go | 11 ++ actor/v7pushaction/actor.go | 1 + .../handle_log_rate_limit_override.go | 24 +++ .../handle_log_rate_limit_override_test.go | 149 +++++++++++++++++ actor/v7pushaction/handle_memory_override.go | 1 - actor/v7pushaction/push_plan.go | 1 + .../ccv3/organization_quota_test.go | 39 +++-- api/cloudcontroller/ccv3/process_instance.go | 16 +- .../ccv3/process_instance_test.go | 12 +- api/cloudcontroller/ccv3/process_test.go | 41 +++-- api/cloudcontroller/ccv3/space_quota_test.go | 54 +++++-- .../ccversion/minimum_version.go | 2 + command/common/command_list_v7.go | 2 +- command/flag/bytes_with_unlimited.go | 59 +++++++ command/flag/bytes_with_unlimited_test.go | 148 +++++++++++++++++ ...limited.go => megabytes_with_unlimited.go} | 6 +- ...st.go => megabytes_with_unlimited_test.go} | 50 +++--- command/v7/create_org_quota_command.go | 33 ++-- command/v7/create_org_quota_command_test.go | 9 +- command/v7/create_space_quota_command.go | 23 +-- command/v7/create_space_quota_command_test.go | 6 +- command/v7/org_quota_command_test.go | 2 + command/v7/org_quotas_command_test.go | 11 +- command/v7/push_command.go | 4 +- command/v7/push_command_test.go | 4 +- command/v7/run_task_command.go | 20 ++- command/v7/run_task_command_test.go | 43 +++++ command/v7/scale_command.go | 33 ++-- command/v7/scale_command_test.go | 150 +++++++++++++----- command/v7/shared/app_summary_displayer.go | 13 ++ .../v7/shared/app_summary_displayer_test.go | 137 +++++++++++----- command/v7/shared/quota_displayer.go | 20 ++- command/v7/space_quota_command_test.go | 2 + command/v7/space_quotas_command_test.go | 10 +- command/v7/update_org_quota_command.go | 27 ++-- command/v7/update_org_quota_command_test.go | 12 +- command/v7/update_space_quota_command.go | 27 ++-- command/v7/update_space_quota_command_test.go | 12 +- integration/helpers/app.go | 14 ++ integration/helpers/app_instance_table.go | 8 +- .../helpers/app_instance_table_test.go | 42 ++--- .../v7/global/org_quota_command_test.go | 1 + .../v7/global/org_quotas_command_test.go | 4 +- integration/v7/isolated/app_command_test.go | 2 +- .../isolated/create_org_quota_command_test.go | 53 ++++++- .../create_space_quota_command_test.go | 23 ++- .../v7/isolated/restart_command_test.go | 26 +-- .../v7/isolated/run_task_command_test.go | 30 +++- integration/v7/isolated/scale_command_test.go | 41 ++++- .../v7/isolated/space_quota_command_test.go | 1 + .../v7/isolated/space_quotas_command_test.go | 4 +- integration/v7/isolated/start_command_test.go | 4 +- .../isolated/update_org_quota_command_test.go | 22 ++- .../update_space_quota_command_test.go | 23 ++- .../combination_manifest_and_flag_test.go | 1 + integration/v7/push/help_test.go | 7 +- .../v7/push/log_rate_limit_flag_test.go | 35 ++++ resources/process_resource.go | 33 ++-- resources/process_resource_test.go | 22 +++ resources/quota_resource.go | 8 + resources/quota_resource_test.go | 24 ++- resources/space_quota_resource.go | 3 + resources/task_resource.go | 2 + util/manifestparser/application.go | 1 + util/manifestparser/application_test.go | 29 ++++ util/manifestparser/process.go | 1 + util/manifestparser/process_test.go | 1 - 70 files changed, 1382 insertions(+), 318 deletions(-) create mode 100644 actor/v7pushaction/handle_log_rate_limit_override.go create mode 100644 actor/v7pushaction/handle_log_rate_limit_override_test.go create mode 100644 command/flag/bytes_with_unlimited.go create mode 100644 command/flag/bytes_with_unlimited_test.go rename command/flag/{memory_with_unlimited.go => megabytes_with_unlimited.go} (63%) rename command/flag/{memory_with_unlimited_test.go => megabytes_with_unlimited_test.go} (58%) create mode 100644 integration/v7/push/log_rate_limit_flag_test.go diff --git a/actor/v7action/organization_quota.go b/actor/v7action/organization_quota.go index 1352d43dc46..2e2f7953b4f 100644 --- a/actor/v7action/organization_quota.go +++ b/actor/v7action/organization_quota.go @@ -15,6 +15,7 @@ type QuotaLimits struct { TotalServiceInstances *types.NullInt TotalRoutes *types.NullInt TotalReservedPorts *types.NullInt + TotalLogVolume *types.NullInt } func (actor Actor) ApplyOrganizationQuotaByName(quotaName string, orgGUID string) (Warnings, error) { @@ -119,6 +120,7 @@ func createQuotaStruct(name string, limits QuotaLimits) resources.OrganizationQu TotalMemory: limits.TotalMemoryInMB, InstanceMemory: limits.PerProcessMemoryInMB, TotalAppInstances: limits.TotalInstances, + TotalLogVolume: limits.TotalLogVolume, } ServiceLimit := resources.ServiceLimit{ TotalServiceInstances: limits.TotalServiceInstances, @@ -164,6 +166,7 @@ func convertUnlimitedToNil(apps *resources.AppLimit, routes *resources.RouteLimi apps.TotalMemory, apps.InstanceMemory, apps.TotalAppInstances, + apps.TotalLogVolume, services.TotalServiceInstances, routes.TotalRoutes, routes.TotalReservedPorts, diff --git a/actor/v7action/organization_quota_test.go b/actor/v7action/organization_quota_test.go index 6cfe1fd21a9..8ff8a897296 100644 --- a/actor/v7action/organization_quota_test.go +++ b/actor/v7action/organization_quota_test.go @@ -399,6 +399,7 @@ var _ = Describe("Organization Quota Actions", func() { PaidServicesAllowed: &trueValue, TotalRoutes: &types.NullInt{Value: 6, IsSet: true}, TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 512, IsSet: true}, } }) @@ -438,6 +439,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: true}, InstanceMemory: nil, TotalAppInstances: nil, + TotalLogVolume: nil, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, @@ -480,6 +482,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalServiceInstances: &types.NullInt{Value: -1, IsSet: true}, TotalRoutes: &types.NullInt{Value: -1, IsSet: true}, TotalReservedPorts: &types.NullInt{Value: -1, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: -1, IsSet: true}, } ccv3Quota = resources.OrganizationQuota{ Quota: resources.Quota{ @@ -488,6 +491,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: false}, InstanceMemory: &types.NullInt{Value: 0, IsSet: false}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: false}, @@ -506,7 +510,7 @@ var _ = Describe("Organization Quota Actions", func() { ) }) - It("call the create endpoint with the respective values and returns warnings", func() { + It("calls the create endpoint with the respective values and returns warnings", func() { Expect(fakeCloudControllerClient.CreateOrganizationQuotaCallCount()).To(Equal(1)) Expect(warnings).To(ConsistOf("some-quota-warning")) @@ -516,7 +520,7 @@ var _ = Describe("Organization Quota Actions", func() { }) }) - When("The create org quota endpoint succeeds", func() { + When("the create org quota endpoint succeeds", func() { var ( ccv3Quota resources.OrganizationQuota ) @@ -528,6 +532,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 512, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, @@ -546,7 +551,7 @@ var _ = Describe("Organization Quota Actions", func() { ) }) - It("call the create endpoint with the respective values and returns warnings", func() { + It("calls the create endpoint with the respective values and returns warnings", func() { Expect(fakeCloudControllerClient.CreateOrganizationQuotaCallCount()).To(Equal(1)) Expect(warnings).To(ConsistOf("some-quota-warning")) @@ -577,6 +582,7 @@ var _ = Describe("Organization Quota Actions", func() { PaidServicesAllowed: &trueValue, TotalRoutes: &types.NullInt{Value: 6, IsSet: true}, TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 64, IsSet: true}, } fakeCloudControllerClient.GetOrganizationQuotasReturns( @@ -622,6 +628,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalMemory: nil, InstanceMemory: nil, TotalAppInstances: nil, + TotalLogVolume: nil, }, Services: resources.ServiceLimit{ TotalServiceInstances: nil, @@ -669,6 +676,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalServiceInstances: &types.NullInt{Value: -1, IsSet: true}, TotalRoutes: &types.NullInt{Value: -1, IsSet: true}, TotalReservedPorts: &types.NullInt{Value: -1, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: -1, IsSet: true}, } ccv3Quota = resources.OrganizationQuota{ Quota: resources.Quota{ @@ -677,6 +685,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: false}, InstanceMemory: &types.NullInt{Value: 0, IsSet: false}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: false}, @@ -722,6 +731,7 @@ var _ = Describe("Organization Quota Actions", func() { TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 64, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, diff --git a/actor/v7action/space_quota.go b/actor/v7action/space_quota.go index 78f5acc38c6..d7aaf5af9ea 100644 --- a/actor/v7action/space_quota.go +++ b/actor/v7action/space_quota.go @@ -31,6 +31,7 @@ func (actor Actor) CreateSpaceQuota(spaceQuotaName string, orgGuid string, limit TotalMemory: limits.TotalMemoryInMB, InstanceMemory: limits.PerProcessMemoryInMB, TotalAppInstances: limits.TotalInstances, + TotalLogVolume: limits.TotalLogVolume, }, Services: resources.ServiceLimit{ TotalServiceInstances: limits.TotalServiceInstances, @@ -137,6 +138,7 @@ func (actor Actor) UpdateSpaceQuota(currentName, orgGUID, newName string, limits TotalMemory: limits.TotalMemoryInMB, InstanceMemory: limits.PerProcessMemoryInMB, TotalAppInstances: limits.TotalInstances, + TotalLogVolume: limits.TotalLogVolume, }, Services: resources.ServiceLimit{ TotalServiceInstances: limits.TotalServiceInstances, diff --git a/actor/v7action/space_quota_test.go b/actor/v7action/space_quota_test.go index fed2d91c0c2..001c70f64ca 100644 --- a/actor/v7action/space_quota_test.go +++ b/actor/v7action/space_quota_test.go @@ -151,6 +151,7 @@ var _ = Describe("Space Quota Actions", func() { TotalServiceInstances: &types.NullInt{IsSet: true, Value: 6}, TotalRoutes: &types.NullInt{IsSet: true, Value: 8}, TotalReservedPorts: &types.NullInt{IsSet: true, Value: 9}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 10}, } }) @@ -167,6 +168,7 @@ var _ = Describe("Space Quota Actions", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2}, InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 10}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{IsSet: true, Value: 6}, @@ -201,6 +203,7 @@ var _ = Describe("Space Quota Actions", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: true}, InstanceMemory: nil, TotalAppInstances: nil, + TotalLogVolume: nil, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, @@ -244,6 +247,7 @@ var _ = Describe("Space Quota Actions", func() { TotalServiceInstances: &types.NullInt{Value: -1, IsSet: true}, TotalRoutes: &types.NullInt{Value: -1, IsSet: true}, TotalReservedPorts: &types.NullInt{Value: -1, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: -1, IsSet: true}, } ccv3Quota = resources.SpaceQuota{ Quota: resources.Quota{ @@ -252,6 +256,7 @@ var _ = Describe("Space Quota Actions", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: false}, InstanceMemory: &types.NullInt{Value: 0, IsSet: false}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: false}, @@ -607,6 +612,7 @@ var _ = Describe("Space Quota Actions", func() { PaidServicesAllowed: &trueValue, TotalRoutes: &types.NullInt{Value: 6, IsSet: true}, TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 512, IsSet: true}, } fakeCloudControllerClient.GetSpaceQuotasReturns( @@ -652,6 +658,7 @@ var _ = Describe("Space Quota Actions", func() { TotalMemory: nil, InstanceMemory: nil, TotalAppInstances: nil, + TotalLogVolume: nil, }, Services: resources.ServiceLimit{ TotalServiceInstances: nil, @@ -699,6 +706,7 @@ var _ = Describe("Space Quota Actions", func() { TotalServiceInstances: &types.NullInt{Value: -1, IsSet: true}, TotalRoutes: &types.NullInt{Value: -1, IsSet: true}, TotalReservedPorts: &types.NullInt{Value: -1, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: -1, IsSet: true}, } ccv3Quota = resources.SpaceQuota{ @@ -708,6 +716,7 @@ var _ = Describe("Space Quota Actions", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: false}, InstanceMemory: &types.NullInt{Value: 0, IsSet: false}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: false}, @@ -754,6 +763,7 @@ var _ = Describe("Space Quota Actions", func() { TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 512, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, @@ -802,6 +812,7 @@ var _ = Describe("Space Quota Actions", func() { TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 512, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, diff --git a/actor/v7pushaction/actor.go b/actor/v7pushaction/actor.go index 00438dea3b2..f2c74ef9cea 100644 --- a/actor/v7pushaction/actor.go +++ b/actor/v7pushaction/actor.go @@ -50,6 +50,7 @@ func NewActor(v3Actor V7Actor, sharedActor SharedActor) *Actor { HandleHealthCheckTimeoutOverride, HandleMemoryOverride, HandleDiskOverride, + HandleLogRateLimitOverride, HandleNoRouteOverride, HandleRandomRouteOverride, HandleTaskOverride, diff --git a/actor/v7pushaction/handle_log_rate_limit_override.go b/actor/v7pushaction/handle_log_rate_limit_override.go new file mode 100644 index 00000000000..9e80cbfc805 --- /dev/null +++ b/actor/v7pushaction/handle_log_rate_limit_override.go @@ -0,0 +1,24 @@ +package v7pushaction + +import ( + "code.cloudfoundry.org/cli/command/translatableerror" + "code.cloudfoundry.org/cli/util/manifestparser" +) + +func HandleLogRateLimitOverride(manifest manifestparser.Manifest, overrides FlagOverrides) (manifestparser.Manifest, error) { + if overrides.LogRateLimit != "" { + if manifest.ContainsMultipleApps() { + return manifest, translatableerror.CommandLineArgsWithMultipleAppsError{} + } + + webProcess := manifest.GetFirstAppWebProcess() + if webProcess != nil { + webProcess.LogRateLimit = overrides.LogRateLimit + } else { + app := manifest.GetFirstApp() + app.LogRateLimit = overrides.LogRateLimit + } + } + + return manifest, nil +} diff --git a/actor/v7pushaction/handle_log_rate_limit_override_test.go b/actor/v7pushaction/handle_log_rate_limit_override_test.go new file mode 100644 index 00000000000..877b1f7d07a --- /dev/null +++ b/actor/v7pushaction/handle_log_rate_limit_override_test.go @@ -0,0 +1,149 @@ +package v7pushaction_test + +import ( + "code.cloudfoundry.org/cli/command/translatableerror" + "code.cloudfoundry.org/cli/util/manifestparser" + + . "code.cloudfoundry.org/cli/actor/v7pushaction" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("HandleLogRateLimitOverride", func() { + var ( + originalManifest manifestparser.Manifest + transformedManifest manifestparser.Manifest + overrides FlagOverrides + executeErr error + ) + + BeforeEach(func() { + originalManifest = manifestparser.Manifest{} + overrides = FlagOverrides{} + }) + + JustBeforeEach(func() { + transformedManifest, executeErr = HandleLogRateLimitOverride(originalManifest, overrides) + }) + + When("log rate limit is not set on a flag override", func() { + BeforeEach(func() { + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + {Type: "worker", LogRateLimit: "1B"}, + }, + }, + } + }) + + It("does not change the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web"}, + {Type: "worker", LogRateLimit: "1B"}, + }, + }, + )) + }) + }) + + When("manifest web process does not specify log rate limit", func() { + BeforeEach(func() { + overrides.LogRateLimit = "64K" + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "web"}, + }, + }, + } + }) + + It("changes the log rate limit of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "web", LogRateLimit: "64K"}, + }, + }, + )) + }) + }) + + When("manifest app has only non-web processes", func() { + BeforeEach(func() { + overrides.LogRateLimit = "32B" + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + } + }) + + It("changes the log rate limit of the app in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + LogRateLimit: "32B", + Processes: []manifestparser.Process{ + {Type: "worker"}, + }, + }, + )) + }) + }) + + When("manifest app has web and non-web processes", func() { + BeforeEach(func() { + overrides.LogRateLimit = "4MB" + + originalManifest.Applications = []manifestparser.Application{ + { + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web"}, + }, + LogRateLimit: "1GB", + }, + } + }) + + It("changes the log rate limit of the web process in the manifest", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(transformedManifest.Applications).To(ConsistOf( + manifestparser.Application{ + Processes: []manifestparser.Process{ + {Type: "worker"}, + {Type: "web", LogRateLimit: "4MB"}, + }, + LogRateLimit: "1GB", + }, + )) + }) + }) + + When("there are multiple apps in the manifest", func() { + BeforeEach(func() { + overrides.LogRateLimit = "64M" + + originalManifest.Applications = []manifestparser.Application{ + {}, + {}, + } + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.CommandLineArgsWithMultipleAppsError{})) + }) + }) +}) diff --git a/actor/v7pushaction/handle_memory_override.go b/actor/v7pushaction/handle_memory_override.go index 82944bba6eb..67c12f758be 100644 --- a/actor/v7pushaction/handle_memory_override.go +++ b/actor/v7pushaction/handle_memory_override.go @@ -1,7 +1,6 @@ package v7pushaction import ( - //"code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/util/manifestparser" ) diff --git a/actor/v7pushaction/push_plan.go b/actor/v7pushaction/push_plan.go index 19d4a32d298..eacb52d577a 100644 --- a/actor/v7pushaction/push_plan.go +++ b/actor/v7pushaction/push_plan.go @@ -59,6 +59,7 @@ type FlagOverrides struct { Vars []template.VarKV NoManifest bool Task bool + LogRateLimit string } func (state PushPlan) String() string { diff --git a/api/cloudcontroller/ccv3/organization_quota_test.go b/api/cloudcontroller/ccv3/organization_quota_test.go index 9d509d62a9f..46460826eec 100644 --- a/api/cloudcontroller/ccv3/organization_quota_test.go +++ b/api/cloudcontroller/ccv3/organization_quota_test.go @@ -60,7 +60,8 @@ var _ = Describe("Organization Quotas", func() { "total_memory_in_mb": 5120, "per_process_memory_in_mb": 1024, "total_instances": 10, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": true, @@ -103,7 +104,8 @@ var _ = Describe("Organization Quotas", func() { "total_memory_in_mb": 10240, "per_process_memory_in_mb": 1024, "total_instances": 8, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 16 }, "services": { "paid_services_allowed": false, @@ -156,6 +158,7 @@ var _ = Describe("Organization Quotas", func() { TotalMemory: &types.NullInt{Value: 5120, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 10, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 10, IsSet: true}, @@ -175,6 +178,7 @@ var _ = Describe("Organization Quotas", func() { TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 16, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, @@ -211,7 +215,8 @@ var _ = Describe("Organization Quotas", func() { "total_memory_in_mb": 10240, "per_process_memory_in_mb": 1024, "total_instances": 8, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": false, @@ -257,6 +262,7 @@ var _ = Describe("Organization Quotas", func() { TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, @@ -337,7 +343,8 @@ var _ = Describe("Organization Quotas", func() { "total_memory_in_mb": 5120, "per_process_memory_in_mb": 1024, "total_instances": 10, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": true, @@ -384,6 +391,7 @@ var _ = Describe("Organization Quotas", func() { TotalMemory: &types.NullInt{Value: 5120, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 10, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 10, IsSet: true}, @@ -460,6 +468,7 @@ var _ = Describe("Organization Quotas", func() { TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, @@ -488,7 +497,8 @@ var _ = Describe("Organization Quotas", func() { "total_memory_in_mb": 2048, "per_process_memory_in_mb": 1024, "total_instances": null, - "per_app_tasks": null + "per_app_tasks": null, + "log_rate_limit_in_bytes_per_second": null }, "services": { "paid_services_allowed": true, @@ -512,9 +522,10 @@ var _ = Describe("Organization Quotas", func() { expectedBody := map[string]interface{}{ "name": "elephant-trunk", "apps": map[string]interface{}{ - "total_memory_in_mb": 2048, - "per_process_memory_in_mb": 1024, - "total_instances": nil, + "total_memory_in_mb": 2048, + "per_process_memory_in_mb": 1024, + "total_instances": nil, + "log_rate_limit_in_bytes_per_second": nil, }, "services": map[string]interface{}{ "paid_services_allowed": true, @@ -711,6 +722,7 @@ var _ = Describe("Organization Quotas", func() { TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, @@ -735,7 +747,8 @@ var _ = Describe("Organization Quotas", func() { "total_memory_in_mb": 2048, "per_process_memory_in_mb": 1024, "total_instances": null, - "per_app_tasks": null + "per_app_tasks": null, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": true, @@ -759,9 +772,10 @@ var _ = Describe("Organization Quotas", func() { expectedBody := map[string]interface{}{ "name": "elephant-trunk", "apps": map[string]interface{}{ - "total_memory_in_mb": 2048, - "per_process_memory_in_mb": 1024, - "total_instances": nil, + "total_memory_in_mb": 2048, + "per_process_memory_in_mb": 1024, + "total_instances": nil, + "log_rate_limit_in_bytes_per_second": 8, }, "services": map[string]interface{}{ "paid_services_allowed": true, @@ -791,6 +805,7 @@ var _ = Describe("Organization Quotas", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2048}, InstanceMemory: &types.NullInt{IsSet: true, Value: 1024}, TotalAppInstances: &types.NullInt{IsSet: false, Value: 0}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{IsSet: true, Value: 0}, diff --git a/api/cloudcontroller/ccv3/process_instance.go b/api/cloudcontroller/ccv3/process_instance.go index 572ce04b8e3..56f7c221b77 100644 --- a/api/cloudcontroller/ccv3/process_instance.go +++ b/api/cloudcontroller/ccv3/process_instance.go @@ -29,8 +29,12 @@ type ProcessInstance struct { IsolationSegment string // MemoryQuota is the maximum memory the instance is allowed to use. MemoryQuota uint64 - // DiskUsage is the current memory usage of the instance. + // MemoryUsage is the current memory usage of the instance. MemoryUsage uint64 + // LogRateLimit is the maximum rate that the instance is allowed to log. + LogRateLimit int64 + // LogRate is the current rate that the instance is logging. + LogRate uint64 // State is the state of the instance. State constant.ProcessInstanceState // Type is the process type for the instance. @@ -47,13 +51,15 @@ func (instance *ProcessInstance) UnmarshalJSON(data []byte) error { Index int64 `json:"index"` IsolationSegment string `json:"isolation_segment"` MemQuota uint64 `json:"mem_quota"` + LogRateLimit int64 `json:"log_rate_limit"` State string `json:"state"` Type string `json:"type"` Uptime int64 `json:"uptime"` Usage struct { - CPU float64 `json:"cpu"` - Mem uint64 `json:"mem"` - Disk uint64 `json:"disk"` + CPU float64 `json:"cpu"` + Mem uint64 `json:"mem"` + Disk uint64 `json:"disk"` + LogRate uint64 `json:"log_rate"` } `json:"usage"` } @@ -70,6 +76,8 @@ func (instance *ProcessInstance) UnmarshalJSON(data []byte) error { instance.IsolationSegment = inputInstance.IsolationSegment instance.MemoryQuota = inputInstance.MemQuota instance.MemoryUsage = inputInstance.Usage.Mem + instance.LogRateLimit = inputInstance.LogRateLimit + instance.LogRate = inputInstance.Usage.LogRate instance.State = constant.ProcessInstanceState(inputInstance.State) instance.Type = inputInstance.Type instance.Uptime, err = time.ParseDuration(fmt.Sprintf("%ds", inputInstance.Uptime)) diff --git a/api/cloudcontroller/ccv3/process_instance_test.go b/api/cloudcontroller/ccv3/process_instance_test.go index a92935a8da3..2280a979348 100644 --- a/api/cloudcontroller/ccv3/process_instance_test.go +++ b/api/cloudcontroller/ccv3/process_instance_test.go @@ -93,10 +93,12 @@ var _ = Describe("ProcessInstance", func() { "usage": { "cpu": 0.01, "mem": 1000000, - "disk": 2000000 + "disk": 2000000, + "log_rate": 5000 }, "mem_quota": 2000000, "disk_quota": 4000000, + "log_rate_limit": 10000, "isolation_segment": "example_iso_segment", "index": 0, "uptime": 123, @@ -108,10 +110,12 @@ var _ = Describe("ProcessInstance", func() { "usage": { "cpu": 0.02, "mem": 8000000, - "disk": 16000000 + "disk": 16000000, + "log_rate": 32000 }, "mem_quota": 16000000, "disk_quota": 32000000, + "log_rate_limit": 64000, "isolation_segment": "example_iso_segment", "index": 1, "uptime": 456 @@ -139,6 +143,8 @@ var _ = Describe("ProcessInstance", func() { IsolationSegment: "example_iso_segment", MemoryQuota: 2000000, MemoryUsage: 1000000, + LogRateLimit: 10000, + LogRate: 5000, State: constant.ProcessInstanceRunning, Type: "web", Uptime: 123 * time.Second, @@ -151,6 +157,8 @@ var _ = Describe("ProcessInstance", func() { IsolationSegment: "example_iso_segment", MemoryQuota: 16000000, MemoryUsage: 8000000, + LogRateLimit: 64000, + LogRate: 32000, State: constant.ProcessInstanceRunning, Type: "web", Uptime: 456 * time.Second, diff --git a/api/cloudcontroller/ccv3/process_test.go b/api/cloudcontroller/ccv3/process_test.go index 2502c8e57a1..62eb3af85b6 100644 --- a/api/cloudcontroller/ccv3/process_test.go +++ b/api/cloudcontroller/ccv3/process_test.go @@ -41,6 +41,7 @@ var _ = Describe("Process", func() { "instances": 22, "memory_in_mb": 32, "disk_in_mb": 1024, + "log_rate_limit_in_bytes_per_second": 512, "relationships": { "app": { "data": { @@ -76,6 +77,7 @@ var _ = Describe("Process", func() { "Instances": Equal(types.NullInt{Value: 22, IsSet: true}), "MemoryInMB": Equal(types.NullUint64{Value: 32, IsSet: true}), "DiskInMB": Equal(types.NullUint64{Value: 1024, IsSet: true}), + "LogRateLimitInBPS": Equal(types.NullInt{Value: 512, IsSet: true}), "HealthCheckType": Equal(constant.HTTP), "HealthCheckEndpoint": Equal("/health"), "HealthCheckInvocationTimeout": BeEquivalentTo(42), @@ -135,15 +137,17 @@ var _ = Describe("Process", func() { When("providing all scale options", func() { BeforeEach(func() { passedProcess = resources.Process{ - Type: constant.ProcessTypeWeb, - Instances: types.NullInt{Value: 2, IsSet: true}, - MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, - DiskInMB: types.NullUint64{Value: 200, IsSet: true}, + Type: constant.ProcessTypeWeb, + Instances: types.NullInt{Value: 2, IsSet: true}, + MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, + DiskInMB: types.NullUint64{Value: 200, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 256, IsSet: true}, } expectedBody := `{ "instances": 2, "memory_in_mb": 100, - "disk_in_mb": 200 + "disk_in_mb": 200, + "log_rate_limit_in_bytes_per_second": 256 }` response := `{ "guid": "some-process-guid" @@ -168,15 +172,17 @@ var _ = Describe("Process", func() { When("providing all scale options with 0 values", func() { BeforeEach(func() { passedProcess = resources.Process{ - Type: constant.ProcessTypeWeb, - Instances: types.NullInt{Value: 0, IsSet: true}, - MemoryInMB: types.NullUint64{Value: 0, IsSet: true}, - DiskInMB: types.NullUint64{Value: 0, IsSet: true}, + Type: constant.ProcessTypeWeb, + Instances: types.NullInt{Value: 0, IsSet: true}, + MemoryInMB: types.NullUint64{Value: 0, IsSet: true}, + DiskInMB: types.NullUint64{Value: 0, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 0, IsSet: true}, } expectedBody := `{ "instances": 0, "memory_in_mb": 0, - "disk_in_mb": 0 + "disk_in_mb": 0, + "log_rate_limit_in_bytes_per_second": 0 }` response := `{ "guid": "some-process-guid" @@ -296,6 +302,7 @@ var _ = Describe("Process", func() { "instances": 22, "memory_in_mb": 32, "disk_in_mb": 1024, + "log_rate_limit_in_bytes_per_second": 64, "relationships": { "app": { "data": { @@ -331,6 +338,7 @@ var _ = Describe("Process", func() { "Instances": Equal(types.NullInt{Value: 22, IsSet: true}), "MemoryInMB": Equal(types.NullUint64{Value: 32, IsSet: true}), "DiskInMB": Equal(types.NullUint64{Value: 1024, IsSet: true}), + "LogRateLimitInBPS": Equal(types.NullInt{Value: 64, IsSet: true}), "HealthCheckType": Equal(constant.HTTP), "HealthCheckEndpoint": Equal("/health"), "HealthCheckInvocationTimeout": BeEquivalentTo(42), @@ -425,6 +433,7 @@ var _ = Describe("Process", func() { "type": "web", "command": "[PRIVATE DATA HIDDEN IN LISTS]", "memory_in_mb": 32, + "log_rate_limit_in_bytes_per_second": 64, "health_check": { "type": "port", "data": { @@ -438,6 +447,7 @@ var _ = Describe("Process", func() { "type": "worker", "command": "[PRIVATE DATA HIDDEN IN LISTS]", "memory_in_mb": 64, + "log_rate_limit_in_bytes_per_second": 128, "health_check": { "type": "http", "data": { @@ -459,6 +469,7 @@ var _ = Describe("Process", func() { "type": "console", "command": "[PRIVATE DATA HIDDEN IN LISTS]", "memory_in_mb": 128, + "log_rate_limit_in_bytes_per_second": 256, "health_check": { "type": "process", "data": { @@ -493,6 +504,7 @@ var _ = Describe("Process", func() { Type: constant.ProcessTypeWeb, Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 64, IsSet: true}, HealthCheckType: constant.Port, HealthCheckTimeout: 0, }, @@ -501,6 +513,7 @@ var _ = Describe("Process", func() { Type: "worker", Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, MemoryInMB: types.NullUint64{Value: 64, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 128, IsSet: true}, HealthCheckType: constant.HTTP, HealthCheckEndpoint: "/health", HealthCheckTimeout: 60, @@ -510,6 +523,7 @@ var _ = Describe("Process", func() { Type: "console", Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, MemoryInMB: types.NullUint64{Value: 128, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 256, IsSet: true}, HealthCheckType: constant.Process, HealthCheckTimeout: 90, }, @@ -560,6 +574,7 @@ var _ = Describe("Process", func() { "type": "web", "command": "[PRIVATE DATA HIDDEN IN LISTS]", "memory_in_mb": 32, + "log_rate_limit_in_bytes_per_second": 64, "health_check": { "type": "port", "data": { @@ -573,6 +588,7 @@ var _ = Describe("Process", func() { "type": "web", "command": "[PRIVATE DATA HIDDEN IN LISTS]", "memory_in_mb": 32, + "log_rate_limit_in_bytes_per_second": 64, "health_check": { "type": "port", "data": { @@ -586,6 +602,7 @@ var _ = Describe("Process", func() { "type": "worker", "command": "[PRIVATE DATA HIDDEN IN LISTS]", "memory_in_mb": 64, + "log_rate_limit_in_bytes_per_second": 128, "health_check": { "type": "http", "data": { @@ -608,6 +625,7 @@ var _ = Describe("Process", func() { "type": "console", "command": "[PRIVATE DATA HIDDEN IN LISTS]", "memory_in_mb": 128, + "log_rate_limit_in_bytes_per_second": 256, "health_check": { "type": "process", "data": { @@ -661,6 +679,7 @@ var _ = Describe("Process", func() { Type: constant.ProcessTypeWeb, Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 64, IsSet: true}, HealthCheckType: constant.Port, HealthCheckTimeout: 0, }, @@ -669,6 +688,7 @@ var _ = Describe("Process", func() { Type: "worker", Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, MemoryInMB: types.NullUint64{Value: 64, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 128, IsSet: true}, HealthCheckType: constant.HTTP, HealthCheckEndpoint: "/health", HealthCheckTimeout: 60, @@ -678,6 +698,7 @@ var _ = Describe("Process", func() { Type: "console", Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, MemoryInMB: types.NullUint64{Value: 128, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 256, IsSet: true}, HealthCheckType: constant.Process, HealthCheckTimeout: 90, }, diff --git a/api/cloudcontroller/ccv3/space_quota_test.go b/api/cloudcontroller/ccv3/space_quota_test.go index 26ba72d6126..21069e1cf2f 100644 --- a/api/cloudcontroller/ccv3/space_quota_test.go +++ b/api/cloudcontroller/ccv3/space_quota_test.go @@ -130,7 +130,7 @@ var _ = Describe("Space Quotas", func() { }) - Describe("CreateSpaceQuotas", func() { + Describe("CreateSpaceQuota", func() { JustBeforeEach(func() { createdSpaceQuota, warnings, executeErr = client.CreateSpaceQuota(inputSpaceQuota) }) @@ -144,6 +144,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2}, InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, }, Services: resources.ServiceLimit{ PaidServicePlans: &trueValue, @@ -166,7 +167,8 @@ var _ = Describe("Space Quotas", func() { "total_memory_in_mb": 2, "per_process_memory_in_mb": 3, "total_instances": 4, - "per_app_tasks": 900 + "per_app_tasks": 900, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": true, @@ -200,9 +202,10 @@ var _ = Describe("Space Quotas", func() { expectedBody := map[string]interface{}{ "name": "my-space-quota", "apps": map[string]interface{}{ - "total_memory_in_mb": 2, - "per_process_memory_in_mb": 3, - "total_instances": 4, + "total_memory_in_mb": 2, + "per_process_memory_in_mb": 3, + "total_instances": 4, + "log_rate_limit_in_bytes_per_second": 8, }, "services": map[string]interface{}{ "paid_services_allowed": true, @@ -242,6 +245,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2}, InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, }, Services: resources.ServiceLimit{ PaidServicePlans: &trueValue, @@ -266,6 +270,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2}, InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, }, Services: resources.ServiceLimit{ PaidServicePlans: &trueValue, @@ -289,7 +294,8 @@ var _ = Describe("Space Quotas", func() { "total_memory_in_mb": 2, "per_process_memory_in_mb": 3, "total_instances": 4, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": true, @@ -324,9 +330,10 @@ var _ = Describe("Space Quotas", func() { expectedBody := map[string]interface{}{ "name": "my-space-quota", "apps": map[string]interface{}{ - "total_memory_in_mb": 2, - "per_process_memory_in_mb": 3, - "total_instances": 4, + "total_memory_in_mb": 2, + "per_process_memory_in_mb": 3, + "total_instances": 4, + "log_rate_limit_in_bytes_per_second": 8, }, "services": map[string]interface{}{ "paid_services_allowed": true, @@ -372,6 +379,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2}, InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, }, Services: resources.ServiceLimit{ PaidServicePlans: &trueValue, @@ -522,7 +530,8 @@ var _ = Describe("Space Quotas", func() { "total_memory_in_mb": 10240, "per_process_memory_in_mb": 1024, "total_instances": 8, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": false, @@ -566,6 +575,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, @@ -663,7 +673,8 @@ var _ = Describe("Space Quotas", func() { "total_memory_in_mb": 5120, "per_process_memory_in_mb": 1024, "total_instances": 10, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": true, @@ -703,7 +714,8 @@ var _ = Describe("Space Quotas", func() { "total_memory_in_mb": 10240, "per_process_memory_in_mb": 1024, "total_instances": 8, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 16 }, "services": { "paid_services_allowed": false, @@ -756,6 +768,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{Value: 5120, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 10, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 10, IsSet: true}, @@ -776,6 +789,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 16, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, @@ -812,7 +826,8 @@ var _ = Describe("Space Quotas", func() { "total_memory_in_mb": 10240, "per_process_memory_in_mb": 1024, "total_instances": 8, - "per_app_tasks": 5 + "per_app_tasks": 5, + "log_rate_limit_in_bytes_per_second": 8 }, "services": { "paid_services_allowed": false, @@ -858,6 +873,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, @@ -1011,6 +1027,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, @@ -1035,7 +1052,8 @@ var _ = Describe("Space Quotas", func() { "total_memory_in_mb": 2048, "per_process_memory_in_mb": 1024, "total_instances": null, - "per_app_tasks": null + "per_app_tasks": null, + "log_rate_limit_in_bytes_per_second": null }, "services": { "paid_services_allowed": true, @@ -1056,9 +1074,10 @@ var _ = Describe("Space Quotas", func() { expectedBody := map[string]interface{}{ "name": "elephant-trunk", "apps": map[string]interface{}{ - "total_memory_in_mb": 2048, - "per_process_memory_in_mb": 1024, - "total_instances": nil, + "total_memory_in_mb": 2048, + "per_process_memory_in_mb": 1024, + "total_instances": nil, + "log_rate_limit_in_bytes_per_second": nil, }, "services": map[string]interface{}{ "paid_services_allowed": true, @@ -1088,6 +1107,7 @@ var _ = Describe("Space Quotas", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2048}, InstanceMemory: &types.NullInt{IsSet: true, Value: 1024}, TotalAppInstances: &types.NullInt{IsSet: false, Value: 0}, + TotalLogVolume: &types.NullInt{IsSet: false, Value: 0}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{IsSet: true, Value: 0}, diff --git a/api/cloudcontroller/ccversion/minimum_version.go b/api/cloudcontroller/ccversion/minimum_version.go index 524f740604f..5158ab18f06 100644 --- a/api/cloudcontroller/ccversion/minimum_version.go +++ b/api/cloudcontroller/ccversion/minimum_version.go @@ -13,4 +13,6 @@ const ( MinVersionHTTP2RoutingV3 = "3.104.0" MinVersionSpaceSupporterV3 = "3.104.0" + + MinVersionLogRateLimitingV3 = "3.124.0" // TODO: update this when we have a CAPI release ) diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index 588e47e391f..3a2b660f3bd 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -134,7 +134,7 @@ type commandList struct { SSH v7.SSHCommand `command:"ssh" description:"SSH to an application container instance"` SSHCode v7.SSHCodeCommand `command:"ssh-code" description:"Get a one time password for ssh clients"` SSHEnabled v7.SSHEnabledCommand `command:"ssh-enabled" description:"Reports whether SSH is enabled on an application container instance"` - Scale v7.ScaleCommand `command:"scale" description:"Change or view the instance count, disk space limit, and memory limit for an app"` + Scale v7.ScaleCommand `command:"scale" description:"Change or view the instance count, disk space limit, memory limit, and log rate limit for an app"` SecurityGroup v7.SecurityGroupCommand `command:"security-group" description:"Show a single security group"` SecurityGroups v7.SecurityGroupsCommand `command:"security-groups" description:"List all security groups"` Service v7.ServiceCommand `command:"service" description:"Show service instance info"` diff --git a/command/flag/bytes_with_unlimited.go b/command/flag/bytes_with_unlimited.go new file mode 100644 index 00000000000..59becfb3394 --- /dev/null +++ b/command/flag/bytes_with_unlimited.go @@ -0,0 +1,59 @@ +package flag + +import ( + "regexp" + "strings" + + "code.cloudfoundry.org/bytefmt" + "code.cloudfoundry.org/cli/types" + flags "github.com/jessevdk/go-flags" +) + +type BytesWithUnlimited types.NullInt + +var zeroBytes *regexp.Regexp = regexp.MustCompile(`^0[KMGT]?B?$`) +var negativeOneBytes *regexp.Regexp = regexp.MustCompile(`^-1[KMGT]?B?$`) + +func (m *BytesWithUnlimited) UnmarshalFlag(val string) error { + if val == "" { + return nil + } + + if negativeOneBytes.MatchString(val) { + m.Value = -1 + m.IsSet = true + return nil + } + + if zeroBytes.MatchString(val) { + m.Value = 0 + m.IsSet = true + return nil + } + + size, err := ConvertToBytes(val) + if err != nil { + return err + } + + m.Value = int(size) + m.IsSet = true + + return nil +} + +func (m *BytesWithUnlimited) IsValidValue(val string) error { + return m.UnmarshalFlag(val) +} + +func ConvertToBytes(val string) (uint64, error) { + size, err := bytefmt.ToBytes(val) + + if err != nil || strings.Contains(strings.ToLower(val), ".") { + return size, &flags.Error{ + Type: flags.ErrRequired, + Message: `Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB`, + } + } + return size, nil +} diff --git a/command/flag/bytes_with_unlimited_test.go b/command/flag/bytes_with_unlimited_test.go new file mode 100644 index 00000000000..29cf00c2d79 --- /dev/null +++ b/command/flag/bytes_with_unlimited_test.go @@ -0,0 +1,148 @@ +package flag_test + +import ( + . "code.cloudfoundry.org/cli/command/flag" + flags "github.com/jessevdk/go-flags" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("BytesWithUnlimited", func() { + var bytesWithUnlimited BytesWithUnlimited + + Describe("UnmarshalFlag", func() { + BeforeEach(func() { + bytesWithUnlimited = BytesWithUnlimited{} + }) + + When("the suffix is B", func() { + It("interprets the number as bytes", func() { + err := bytesWithUnlimited.UnmarshalFlag("17B") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(17)) + }) + }) + + When("the suffix is K", func() { + It("interprets the number as kilobytes", func() { + err := bytesWithUnlimited.UnmarshalFlag("64K") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(64 * 1024)) + }) + }) + + When("the suffix is KB", func() { + It("interprets the number as kilobytes", func() { + err := bytesWithUnlimited.UnmarshalFlag("64KB") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(64 * 1024)) + }) + }) + + When("the suffix is M", func() { + It("interprets the number as megabytes", func() { + err := bytesWithUnlimited.UnmarshalFlag("17M") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(17 * 1024 * 1024)) + }) + }) + + When("the suffix is MB", func() { + It("interprets the number as megabytes", func() { + err := bytesWithUnlimited.UnmarshalFlag("19MB") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(19 * 1024 * 1024)) + }) + }) + + When("the suffix is G", func() { + It("interprets the number as gigabytes", func() { + err := bytesWithUnlimited.UnmarshalFlag("2G") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(2 * 1024 * 1024 * 1024)) + }) + }) + + When("the suffix is GB", func() { + It("interprets the number as gigabytes", func() { + err := bytesWithUnlimited.UnmarshalFlag("3GB") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(3 * 1024 * 1024 * 1024)) + }) + }) + + When("the value is -1 (unlimited)", func() { + It("interprets the number as -1 (unlimited)", func() { + err := bytesWithUnlimited.UnmarshalFlag("-1") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(-1)) + }) + + It("accepts units", func() { + err := bytesWithUnlimited.UnmarshalFlag("-1T") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(-1)) + }) + }) + + When("the value is 0", func() { + It("set's the value to 0", func() { + err := bytesWithUnlimited.UnmarshalFlag("0") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(0)) + }) + + It("accepts units", func() { + err := bytesWithUnlimited.UnmarshalFlag("0TB") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(0)) + }) + }) + + When("the suffix is lowercase", func() { + It("is case insensitive", func() { + err := bytesWithUnlimited.UnmarshalFlag("7m") + Expect(err).ToNot(HaveOccurred()) + Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(7 * 1024 * 1024)) + }) + }) + + When("the value is invalid", func() { + It("returns an error", func() { + err := bytesWithUnlimited.UnmarshalFlag("invalid") + Expect(err).To(MatchError(&flags.Error{ + Type: flags.ErrRequired, + Message: `Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB`, + })) + }) + }) + + When("a decimal is used", func() { + It("returns an error", func() { + err := bytesWithUnlimited.UnmarshalFlag("1.2M") + Expect(err).To(MatchError(&flags.Error{ + Type: flags.ErrRequired, + Message: `Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB`, + })) + }) + }) + + When("the units are missing", func() { + It("returns an error", func() { + err := bytesWithUnlimited.UnmarshalFlag("37") + Expect(err).To(MatchError(&flags.Error{ + Type: flags.ErrRequired, + Message: `Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB`, + })) + }) + }) + + When("value is empty", func() { + It("sets IsSet to false", func() { + err := bytesWithUnlimited.UnmarshalFlag("") + Expect(err).NotTo(HaveOccurred()) + Expect(bytesWithUnlimited.IsSet).To(BeFalse()) + }) + }) + }) +}) diff --git a/command/flag/memory_with_unlimited.go b/command/flag/megabytes_with_unlimited.go similarity index 63% rename from command/flag/memory_with_unlimited.go rename to command/flag/megabytes_with_unlimited.go index 10fe06b32f6..de5595cc99a 100644 --- a/command/flag/memory_with_unlimited.go +++ b/command/flag/megabytes_with_unlimited.go @@ -4,9 +4,9 @@ import ( "code.cloudfoundry.org/cli/types" ) -type MemoryWithUnlimited types.NullInt +type MegabytesWithUnlimited types.NullInt -func (m *MemoryWithUnlimited) UnmarshalFlag(val string) error { +func (m *MegabytesWithUnlimited) UnmarshalFlag(val string) error { if val == "" { return nil } @@ -28,6 +28,6 @@ func (m *MemoryWithUnlimited) UnmarshalFlag(val string) error { return nil } -func (m *MemoryWithUnlimited) IsValidValue(val string) error { +func (m *MegabytesWithUnlimited) IsValidValue(val string) error { return m.UnmarshalFlag(val) } diff --git a/command/flag/memory_with_unlimited_test.go b/command/flag/megabytes_with_unlimited_test.go similarity index 58% rename from command/flag/memory_with_unlimited_test.go rename to command/flag/megabytes_with_unlimited_test.go index 65c4e92d939..3d47ba73e31 100644 --- a/command/flag/memory_with_unlimited_test.go +++ b/command/flag/megabytes_with_unlimited_test.go @@ -7,65 +7,65 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("MemoryWithUnlimited", func() { - var memoryWithUnlimited MemoryWithUnlimited +var _ = Describe("MegabytesWithUnlimited", func() { + var megabytesWithUnlimited MegabytesWithUnlimited Describe("UnmarshalFlag", func() { BeforeEach(func() { - memoryWithUnlimited = MemoryWithUnlimited{} + megabytesWithUnlimited = MegabytesWithUnlimited{} }) When("the suffix is M", func() { - It("interprets the number as memoryWithUnlimited", func() { - err := memoryWithUnlimited.UnmarshalFlag("17M") + It("interprets the number as megabytesWithUnlimited", func() { + err := megabytesWithUnlimited.UnmarshalFlag("17M") Expect(err).ToNot(HaveOccurred()) - Expect(memoryWithUnlimited.Value).To(BeEquivalentTo(17)) + Expect(megabytesWithUnlimited.Value).To(BeEquivalentTo(17)) }) }) When("the suffix is MB", func() { - It("interprets the number as memoryWithUnlimited", func() { - err := memoryWithUnlimited.UnmarshalFlag("19MB") + It("interprets the number as megabytesWithUnlimited", func() { + err := megabytesWithUnlimited.UnmarshalFlag("19MB") Expect(err).ToNot(HaveOccurred()) - Expect(memoryWithUnlimited.Value).To(BeEquivalentTo(19)) + Expect(megabytesWithUnlimited.Value).To(BeEquivalentTo(19)) }) }) When("the suffix is G", func() { It("interprets the number as gigabytes", func() { - err := memoryWithUnlimited.UnmarshalFlag("2G") + err := megabytesWithUnlimited.UnmarshalFlag("2G") Expect(err).ToNot(HaveOccurred()) - Expect(memoryWithUnlimited.Value).To(BeEquivalentTo(2048)) + Expect(megabytesWithUnlimited.Value).To(BeEquivalentTo(2048)) }) }) When("the suffix is GB", func() { It("interprets the number as gigabytes", func() { - err := memoryWithUnlimited.UnmarshalFlag("3GB") + err := megabytesWithUnlimited.UnmarshalFlag("3GB") Expect(err).ToNot(HaveOccurred()) - Expect(memoryWithUnlimited.Value).To(BeEquivalentTo(3072)) + Expect(megabytesWithUnlimited.Value).To(BeEquivalentTo(3072)) }) }) When("the value is -1 (unlimited)", func() { - It("interprets the number as memoryWithUnlimited", func() { - err := memoryWithUnlimited.UnmarshalFlag("-1") + It("interprets the number as megabytesWithUnlimited", func() { + err := megabytesWithUnlimited.UnmarshalFlag("-1") Expect(err).ToNot(HaveOccurred()) - Expect(memoryWithUnlimited.Value).To(BeEquivalentTo(-1)) + Expect(megabytesWithUnlimited.Value).To(BeEquivalentTo(-1)) }) }) When("the suffix is lowercase", func() { It("is case insensitive", func() { - err := memoryWithUnlimited.UnmarshalFlag("7m") + err := megabytesWithUnlimited.UnmarshalFlag("7m") Expect(err).ToNot(HaveOccurred()) - Expect(memoryWithUnlimited.Value).To(BeEquivalentTo(7)) + Expect(megabytesWithUnlimited.Value).To(BeEquivalentTo(7)) }) }) - When("the memoryWithUnlimited are invalid", func() { + When("the megabytesWithUnlimited are invalid", func() { It("returns an error", func() { - err := memoryWithUnlimited.UnmarshalFlag("invalid") + err := megabytesWithUnlimited.UnmarshalFlag("invalid") Expect(err).To(MatchError(&flags.Error{ Type: flags.ErrRequired, Message: `Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB`, @@ -75,7 +75,7 @@ var _ = Describe("MemoryWithUnlimited", func() { When("a decimal is used", func() { It("returns an error", func() { - err := memoryWithUnlimited.UnmarshalFlag("1.2M") + err := megabytesWithUnlimited.UnmarshalFlag("1.2M") Expect(err).To(MatchError(&flags.Error{ Type: flags.ErrRequired, Message: `Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB`, @@ -85,7 +85,7 @@ var _ = Describe("MemoryWithUnlimited", func() { When("the units are missing", func() { It("returns an error", func() { - err := memoryWithUnlimited.UnmarshalFlag("37") + err := megabytesWithUnlimited.UnmarshalFlag("37") Expect(err).To(MatchError(&flags.Error{ Type: flags.ErrRequired, Message: `Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB`, @@ -95,7 +95,7 @@ var _ = Describe("MemoryWithUnlimited", func() { When("the suffix is B", func() { It("returns an error", func() { - err := memoryWithUnlimited.UnmarshalFlag("10B") + err := megabytesWithUnlimited.UnmarshalFlag("10B") Expect(err).To(MatchError(&flags.Error{ Type: flags.ErrRequired, Message: `Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB`, @@ -105,9 +105,9 @@ var _ = Describe("MemoryWithUnlimited", func() { When("value is empty", func() { It("sets IsSet to false", func() { - err := memoryWithUnlimited.UnmarshalFlag("") + err := megabytesWithUnlimited.UnmarshalFlag("") Expect(err).NotTo(HaveOccurred()) - Expect(memoryWithUnlimited.IsSet).To(BeFalse()) + Expect(megabytesWithUnlimited.IsSet).To(BeFalse()) }) }) }) diff --git a/command/v7/create_org_quota_command.go b/command/v7/create_org_quota_command.go index fe34a1de228..b4597e325aa 100644 --- a/command/v7/create_org_quota_command.go +++ b/command/v7/create_org_quota_command.go @@ -10,16 +10,18 @@ import ( type CreateOrgQuotaCommand struct { BaseCommand - RequiredArgs flag.OrganizationQuota `positional-args:"yes"` - NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. (Default: unlimited)."` - PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans. (Default: disallowed)."` - PerProcessMemory flag.MemoryWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). (Default: unlimited)."` - TotalMemory flag.MemoryWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount. (Default: 0)."` - TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount. (Default: 0)."` - TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount. (Default: 0)."` - TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount. (Default: 0)."` - usage interface{} `usage:"CF_NAME create-org-quota ORG_QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS]"` - relatedCommands interface{} `related_commands:"create-org, org-quotas, set-org-quota"` + RequiredArgs flag.OrganizationQuota `positional-args:"yes"` + NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. (Default: unlimited)."` + PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans. (Default: disallowed)."` + PerProcessMemory flag.MegabytesWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). (Default: unlimited)."` + TotalMemory flag.MegabytesWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount. (Default: 0)."` + TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount. (Default: 0)."` + TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount. (Default: 0)."` + TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount. (Default: 0)."` + TotalLogVolume flag.BytesWithUnlimited `short:"l" description:"Total log volume per second all processes can have, in bytes (e.g. 128B, 4K, 1M). -1 represents an unlimited amount. (Default: -1)."` + + usage interface{} `usage:"CF_NAME create-org-quota ORG_QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS] [-l LOG_VOLUME]"` + relatedCommands interface{} `related_commands:"create-org, org-quotas, set-org-quota"` } func (cmd CreateOrgQuotaCommand) Execute(args []string) error { @@ -49,6 +51,7 @@ func (cmd CreateOrgQuotaCommand) Execute(args []string) error { TotalServiceInstances: convertIntegerLimitFlagToNullInt(cmd.TotalServiceInstances), TotalRoutes: convertIntegerLimitFlagToNullInt(cmd.TotalRoutes), TotalReservedPorts: convertIntegerLimitFlagToNullInt(cmd.TotalReservedPorts), + TotalLogVolume: convertBytesFlagToNullInt(cmd.TotalLogVolume), }) cmd.UI.DisplayWarnings(warnings) @@ -66,7 +69,15 @@ func (cmd CreateOrgQuotaCommand) Execute(args []string) error { return nil } -func convertMegabytesFlagToNullInt(flag flag.MemoryWithUnlimited) *types.NullInt { +func convertBytesFlagToNullInt(flag flag.BytesWithUnlimited) *types.NullInt { + if !flag.IsSet { + return nil + } + + return &types.NullInt{IsSet: true, Value: flag.Value} +} + +func convertMegabytesFlagToNullInt(flag flag.MegabytesWithUnlimited) *types.NullInt { if !flag.IsSet { return nil } diff --git a/command/v7/create_org_quota_command_test.go b/command/v7/create_org_quota_command_test.go index d8f66f51fdd..c964bb964f2 100644 --- a/command/v7/create_org_quota_command_test.go +++ b/command/v7/create_org_quota_command_test.go @@ -52,7 +52,6 @@ var _ = Describe("create-org-quota Command", func() { }) JustBeforeEach(func() { - executeErr = cmd.Execute(nil) }) @@ -105,11 +104,12 @@ var _ = Describe("create-org-quota Command", func() { BeforeEach(func() { cmd.PaidServicePlans = true cmd.NumAppInstances = flag.IntegerLimit{IsSet: true, Value: 10} - cmd.PerProcessMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 9} - cmd.TotalMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 2048} + cmd.PerProcessMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 9} + cmd.TotalMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 2048} cmd.TotalRoutes = flag.IntegerLimit{IsSet: true, Value: 7} cmd.TotalReservedPorts = flag.IntegerLimit{IsSet: true, Value: 1} cmd.TotalServiceInstances = flag.IntegerLimit{IsSet: true, Value: 2} + cmd.TotalLogVolume = flag.BytesWithUnlimited{IsSet: true, Value: 8} fakeActor.CreateOrganizationQuotaReturns( v7action.Warnings{"warning"}, nil) @@ -142,6 +142,9 @@ var _ = Describe("create-org-quota Command", func() { Expect(quotaLimits.TotalServiceInstances.IsSet).To(Equal(true)) Expect(quotaLimits.TotalServiceInstances.Value).To(Equal(2)) + Expect(quotaLimits.TotalLogVolume.IsSet).To(Equal(true)) + Expect(quotaLimits.TotalLogVolume.Value).To(Equal(8)) + Expect(testUI.Out).To(Say("Creating org quota %s as bob...", orgQuotaName)) Expect(testUI.Out).To(Say("OK")) }) diff --git a/command/v7/create_space_quota_command.go b/command/v7/create_space_quota_command.go index 892d04675e7..250acde1844 100644 --- a/command/v7/create_space_quota_command.go +++ b/command/v7/create_space_quota_command.go @@ -9,16 +9,18 @@ import ( type CreateSpaceQuotaCommand struct { BaseCommand - RequiredArgs flag.SpaceQuota `positional-args:"yes"` - NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. (Default: unlimited)."` - PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans. (Default: disallowed)."` - PerProcessMemory flag.MemoryWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). (Default: unlimited)."` - TotalMemory flag.MemoryWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount. (Default: 0)."` - TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount. (Default: 0)."` - TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount. (Default: 0)."` - TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount. (Default: 0)."` - usage interface{} `usage:"CF_NAME create-space-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS]"` - relatedCommands interface{} `related_commands:"create-space, space-quotas, set-space-quota"` + RequiredArgs flag.SpaceQuota `positional-args:"yes"` + NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. (Default: unlimited)."` + PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans. (Default: disallowed)."` + PerProcessMemory flag.MegabytesWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). (Default: unlimited)."` + TotalMemory flag.MegabytesWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount. (Default: 0)."` + TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount. (Default: 0)."` + TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount. (Default: 0)."` + TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount. (Default: 0)."` + TotalLogVolume flag.BytesWithUnlimited `short:"l" description:"Total log volume per second all processes can have, in bytes (e.g. 128B, 4K, 1M). -1 represents an unlimited amount. (Default: -1)."` + + usage interface{} `usage:"CF_NAME create-space-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS] [-l LOG_VOLUME]"` + relatedCommands interface{} `related_commands:"create-space, space-quotas, set-space-quota"` } func (cmd CreateSpaceQuotaCommand) Execute([]string) error { @@ -49,6 +51,7 @@ func (cmd CreateSpaceQuotaCommand) Execute([]string) error { TotalServiceInstances: convertIntegerLimitFlagToNullInt(cmd.TotalServiceInstances), TotalRoutes: convertIntegerLimitFlagToNullInt(cmd.TotalRoutes), TotalReservedPorts: convertIntegerLimitFlagToNullInt(cmd.TotalReservedPorts), + TotalLogVolume: convertBytesFlagToNullInt(cmd.TotalLogVolume), }, ) diff --git a/command/v7/create_space_quota_command_test.go b/command/v7/create_space_quota_command_test.go index 6e94f2cb402..49f47c4a627 100644 --- a/command/v7/create_space_quota_command_test.go +++ b/command/v7/create_space_quota_command_test.go @@ -135,13 +135,14 @@ var _ = Describe("create-space-quota Command", func() { When("all flag limits are given", func() { BeforeEach(func() { - cmd.TotalMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 47} - cmd.PerProcessMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 23} + cmd.TotalMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 47} + cmd.PerProcessMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 23} cmd.NumAppInstances = flag.IntegerLimit{IsSet: true, Value: 4} cmd.PaidServicePlans = true cmd.TotalServiceInstances = flag.IntegerLimit{IsSet: true, Value: 9} cmd.TotalRoutes = flag.IntegerLimit{IsSet: true, Value: 1} cmd.TotalReservedPorts = flag.IntegerLimit{IsSet: true, Value: 7} + cmd.TotalLogVolume = flag.BytesWithUnlimited{IsSet: true, Value: 512} fakeActor.CreateSpaceQuotaReturns( v7action.Warnings{"warnings-1", "warnings-2"}, @@ -163,6 +164,7 @@ var _ = Describe("create-space-quota Command", func() { TotalServiceInstances: &types.NullInt{IsSet: true, Value: 9}, TotalRoutes: &types.NullInt{IsSet: true, Value: 1}, TotalReservedPorts: &types.NullInt{IsSet: true, Value: 7}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 512}, })) }) diff --git a/command/v7/org_quota_command_test.go b/command/v7/org_quota_command_test.go index 187fcda8d56..18d29c157c0 100644 --- a/command/v7/org_quota_command_test.go +++ b/command/v7/org_quota_command_test.go @@ -105,6 +105,7 @@ var _ = Describe("Org Quota Command", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2048}, InstanceMemory: &types.NullInt{IsSet: true, Value: 1024}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 2}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 512}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{IsSet: false}, @@ -138,6 +139,7 @@ var _ = Describe("Org Quota Command", func() { Expect(testUI.Out).To(Say(`paid service plans:\s+disallowed`)) Expect(testUI.Out).To(Say(`app instances:\s+2`)) Expect(testUI.Out).To(Say(`route ports:\s+unlimited`)) + Expect(testUI.Out).To(Say(`log volume per second:\s+512B`)) }) }) }) diff --git a/command/v7/org_quotas_command_test.go b/command/v7/org_quotas_command_test.go index d69e632c6f0..e1d20bd4e09 100644 --- a/command/v7/org_quotas_command_test.go +++ b/command/v7/org_quotas_command_test.go @@ -66,6 +66,7 @@ var _ = Describe("org-quotas command", func() { TotalMemory: &types.NullInt{Value: 1048576, IsSet: true}, InstanceMemory: &types.NullInt{Value: 32, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 3, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 512, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 3, IsSet: true}, @@ -91,8 +92,8 @@ var _ = Describe("org-quotas command", func() { It("retrieves and displays all quotas", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(fakeActor.GetOrganizationQuotasCallCount()).To(Equal(1)) - Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports`)) - Expect(testUI.Out).To(Say(`org-quota-1\s+1T\s+32M\s+5\s+3\s+allowed\s+3\s+2`)) + Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports\s+log volume per second`)) + Expect(testUI.Out).To(Say(`org-quota-1\s+1T\s+32M\s+5\s+3\s+allowed\s+3\s+2\s+512B`)) }) When("there are limits that have not been configured", func() { @@ -105,6 +106,7 @@ var _ = Describe("org-quotas command", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: false}, InstanceMemory: &types.NullInt{Value: 0, IsSet: false}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: false}, @@ -123,9 +125,8 @@ var _ = Describe("org-quotas command", func() { It("should convert default values from the API into readable outputs", func() { Expect(executeErr).NotTo(HaveOccurred()) - Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports`)) - Expect(testUI.Out).To(Say(`default\s+unlimited\s+unlimited\s+unlimited\s+unlimited\s+allowed\s+unlimited\s+unlimited`)) - + Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports\s+log volume per second`)) + Expect(testUI.Out).To(Say(`default\s+unlimited\s+unlimited\s+unlimited\s+unlimited\s+allowed\s+unlimited\s+unlimited\s+unlimited`)) }) }) }) diff --git a/command/v7/push_command.go b/command/v7/push_command.go index ea2e9175e48..4df28ebdec6 100644 --- a/command/v7/push_command.go +++ b/command/v7/push_command.go @@ -88,6 +88,7 @@ type PushCommand struct { HealthCheckHTTPEndpoint string `long:"endpoint" description:"Valid path on the app for an HTTP health check. Only used when specifying --health-check-type=http"` HealthCheckType flag.HealthCheckType `long:"health-check-type" short:"u" description:"Application health check type. Defaults to 'port'. 'http' requires a valid endpoint, for example, '/health'."` Instances flag.Instances `long:"instances" short:"i" description:"Number of instances"` + LogRateLimit string `long:"log-rate-limit" short:"l" description:"Log rate limit per second, in bytes (e.g. 128B, 4K, 1M). -l=-1 represents unlimited"` PathToManifest flag.ManifestPathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"` Memory string `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` NoManifest bool `long:"no-manifest" description:"Ignore manifest file"` @@ -103,7 +104,7 @@ type PushCommand struct { Vars []template.VarKV `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"` PathsToVarsFiles []flag.PathWithExistenceCheck `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"` dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` - usage interface{} `usage:"CF_NAME push APP_NAME [-b BUILDPACK_NAME]\n [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n [-u (process | port | http)] [--no-route | --random-route]\n [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n \n CF_NAME push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME]\n [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n [-u (process | port | http)] [--no-route | --random-route ]\n [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]..."` + usage interface{} `usage:"CF_NAME push APP_NAME [-b BUILDPACK_NAME]\n [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n [-k DISK] [-m MEMORY] [-l LOG_RATE_LIMIT] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n [-u (process | port | http)] [--no-route | --random-route]\n [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n \n CF_NAME push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME]\n [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n [-k DISK] [-m MEMORY] [-l LOG_RATE_LIMIT] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n [-u (process | port | http)] [--no-route | --random-route ]\n [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]..."` envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for staging, in minutes" environmentDefault:"15"` envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` @@ -358,6 +359,7 @@ func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) { Vars: cmd.Vars, NoManifest: cmd.NoManifest, Task: cmd.Task, + LogRateLimit: cmd.LogRateLimit, }, nil } diff --git a/command/v7/push_command_test.go b/command/v7/push_command_test.go index 6d5d77fdcef..bedf00eedc0 100644 --- a/command/v7/push_command_test.go +++ b/command/v7/push_command_test.go @@ -319,7 +319,7 @@ var _ = Describe("push Command", func() { }) }) - When("marsahlling the manifest succeeds", func() { + When("marshalling the manifest succeeds", func() { BeforeEach(func() { fakeManifestParser.MarshalManifestReturns([]byte("our-manifest"), nil) }) @@ -1078,6 +1078,7 @@ var _ = Describe("push Command", func() { cmd.PathsToVarsFiles = []flag.PathWithExistenceCheck{"/vars1", "/vars2"} cmd.Vars = []template.VarKV{{Name: "key", Value: "val"}} cmd.Task = true + cmd.LogRateLimit = "512M" }) JustBeforeEach(func() { @@ -1106,6 +1107,7 @@ var _ = Describe("push Command", func() { Expect(overrides.PathsToVarsFiles).To(Equal([]string{"/vars1", "/vars2"})) Expect(overrides.Vars).To(Equal([]template.VarKV{{Name: "key", Value: "val"}})) Expect(overrides.Task).To(BeTrue()) + Expect(overrides.LogRateLimit).To(Equal("512M")) }) When("a docker image is provided", func() { diff --git a/command/v7/run_task_command.go b/command/v7/run_task_command.go index c01bbe2e243..eb07aa4d3f2 100644 --- a/command/v7/run_task_command.go +++ b/command/v7/run_task_command.go @@ -10,14 +10,15 @@ import ( type RunTaskCommand struct { BaseCommand - RequiredArgs flag.RunTaskArgsV7 `positional-args:"yes"` - Command string `long:"command" short:"c" description:"The command to execute"` - Disk flag.Megabytes `short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"` - Memory flag.Megabytes `short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` - Name string `long:"name" description:"Name to give the task (generated if omitted)"` - Process string `long:"process" description:"Process type to use as a template for command, memory, and disk for the created task."` - usage interface{} `usage:"CF_NAME run-task APP_NAME [--command COMMAND] [-k DISK] [-m MEMORY] [--name TASK_NAME] [--process PROCESS_TYPE]\n\nTIP:\n Use 'cf logs' to display the logs of the app and all its tasks. If your task name is unique, grep this command's output for the task name to view task-specific logs.\n\nEXAMPLES:\n CF_NAME run-task my-app --command \"bundle exec rake db:migrate\" --name migrate\n\n CF_NAME run-task my-app --process batch_job\n\n CF_NAME run-task my-app"` - relatedCommands interface{} `related_commands:"logs, tasks, terminate-task"` + RequiredArgs flag.RunTaskArgsV7 `positional-args:"yes"` + Command string `long:"command" short:"c" description:"The command to execute"` + Disk flag.Megabytes `short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"` + LogRateLimit flag.BytesWithUnlimited `short:"l" description:"Log rate limit per second, in bytes (e.g. 128B, 4K, 1M). -l=-1 represents unlimited"` + Memory flag.Megabytes `short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` + Name string `long:"name" description:"Name to give the task (generated if omitted)"` + Process string `long:"process" description:"Process type to use as a template for command, memory, and disk for the created task."` + usage interface{} `usage:"CF_NAME run-task APP_NAME [--command COMMAND] [-k DISK] [-m MEMORY] [-l LOG_RATE_LIMIT] [--name TASK_NAME] [--process PROCESS_TYPE]\n\nTIP:\n Use 'cf logs' to display the logs of the app and all its tasks. If your task name is unique, grep this command's output for the task name to view task-specific logs.\n\nEXAMPLES:\n CF_NAME run-task my-app --command \"bundle exec rake db:migrate\" --name migrate\n\n CF_NAME run-task my-app --process batch_job\n\n CF_NAME run-task my-app"` + relatedCommands interface{} `related_commands:"logs, tasks, terminate-task"` } func (cmd RunTaskCommand) Execute(args []string) error { @@ -59,6 +60,9 @@ func (cmd RunTaskCommand) Execute(args []string) error { if cmd.Memory.IsSet { inputTask.MemoryInMB = cmd.Memory.Value } + if cmd.LogRateLimit.IsSet { + inputTask.LogRateLimitInBPS = cmd.LogRateLimit.Value + } if cmd.Command == "" && cmd.Process == "" { cmd.Process = "task" } diff --git a/command/v7/run_task_command_test.go b/command/v7/run_task_command_test.go index 34233afe90b..585515345a7 100644 --- a/command/v7/run_task_command_test.go +++ b/command/v7/run_task_command_test.go @@ -197,6 +197,49 @@ var _ = Describe("run-task Command", func() { }) }) + When("task log rate limit is provided", func() { + BeforeEach(func() { + cmd.Name = "some-task-name" + cmd.LogRateLimit = flag.BytesWithUnlimited{Value: 1024 * 5, IsSet: true} + fakeActor.RunTaskReturns( + resources.Task{ + Name: "some-task-name", + SequenceID: 3, + }, + v7action.Warnings{"get-application-warning-3"}, + nil) + }) + + It("creates a new task and outputs all warnings", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + appName, spaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(appName).To(Equal("some-app-name")) + Expect(spaceGUID).To(Equal("some-space-guid")) + + Expect(fakeActor.RunTaskCallCount()).To(Equal(1)) + appGUID, task := fakeActor.RunTaskArgsForCall(0) + Expect(appGUID).To(Equal("some-app-guid")) + Expect(task).To(Equal(resources.Task{ + Command: "some command", + Name: "some-task-name", + LogRateLimitInBPS: 1024 * 5, + })) + + Expect(testUI.Out).To(Say("Creating task for app some-app-name in org some-org / space some-space as some-user...")) + Expect(testUI.Out).To(Say("Task has been submitted successfully for execution.")) + Expect(testUI.Out).To(Say("OK")) + + Expect(testUI.Out).To(Say(`task name:\s+some-task-name`)) + Expect(testUI.Out).To(Say(`task id:\s+3`)) + + Expect(testUI.Err).To(Say("get-application-warning-1")) + Expect(testUI.Err).To(Say("get-application-warning-2")) + Expect(testUI.Err).To(Say("get-application-warning-3")) + }) + }) + When("process is provided", func() { BeforeEach(func() { cmd.Name = "some-task-name" diff --git a/command/v7/scale_command.go b/command/v7/scale_command.go index fd0ed4f5469..99bda380f9f 100644 --- a/command/v7/scale_command.go +++ b/command/v7/scale_command.go @@ -7,20 +7,22 @@ import ( "code.cloudfoundry.org/cli/command/translatableerror" "code.cloudfoundry.org/cli/command/v7/shared" "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/types" ) type ScaleCommand struct { BaseCommand - RequiredArgs flag.AppName `positional-args:"yes"` - Force bool `long:"force" short:"f" description:"Force restart of app without prompt"` - Instances flag.Instances `long:"instances" short:"i" required:"false" description:"Number of instances"` - DiskLimit flag.Megabytes `short:"k" required:"false" description:"Disk limit (e.g. 256M, 1024M, 1G)"` - MemoryLimit flag.Megabytes `short:"m" required:"false" description:"Memory limit (e.g. 256M, 1024M, 1G)"` - ProcessType string `long:"process" default:"web" description:"App process to scale"` - usage interface{} `usage:"CF_NAME scale APP_NAME [--process PROCESS] [-i INSTANCES] [-k DISK] [-m MEMORY] [-f]\n\n Modifying the app's disk or memory will cause the app to restart."` - relatedCommands interface{} `related_commands:"push"` - envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` + RequiredArgs flag.AppName `positional-args:"yes"` + Force bool `long:"force" short:"f" description:"Force restart of app without prompt"` + Instances flag.Instances `long:"instances" short:"i" required:"false" description:"Number of instances"` + DiskLimit flag.Megabytes `short:"k" required:"false" description:"Disk limit (e.g. 256M, 1024M, 1G)"` + LogRateLimit flag.BytesWithUnlimited `short:"l" required:"false" description:"Log rate limit per second, in bytes (e.g. 128B, 4K, 1M). -l=-1 represents unlimited"` + MemoryLimit flag.Megabytes `short:"m" required:"false" description:"Memory limit (e.g. 256M, 1024M, 1G)"` + ProcessType string `long:"process" default:"web" description:"App process to scale"` + usage interface{} `usage:"CF_NAME scale APP_NAME [--process PROCESS] [-i INSTANCES] [-k DISK] [-m MEMORY] [-l LOG_RATE_LIMIT] [-f]\n\n Modifying the app's disk, memory, or log rate will cause the app to restart."` + relatedCommands interface{} `related_commands:"push"` + envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` } func (cmd ScaleCommand) Execute(args []string) error { @@ -40,7 +42,7 @@ func (cmd ScaleCommand) Execute(args []string) error { return err } - if !cmd.Instances.IsSet && !cmd.DiskLimit.IsSet && !cmd.MemoryLimit.IsSet { + if !cmd.Instances.IsSet && !cmd.DiskLimit.IsSet && !cmd.MemoryLimit.IsSet && !cmd.LogRateLimit.IsSet { return cmd.showCurrentScale(user.Name, err) } @@ -113,10 +115,11 @@ func (cmd ScaleCommand) scaleProcess(appGUID string, username string) (bool, err } warnings, err := cmd.Actor.ScaleProcessByApplication(appGUID, resources.Process{ - Type: cmd.ProcessType, - Instances: cmd.Instances.NullInt, - MemoryInMB: cmd.MemoryLimit.NullUint64, - DiskInMB: cmd.DiskLimit.NullUint64, + Type: cmd.ProcessType, + Instances: cmd.Instances.NullInt, + MemoryInMB: cmd.MemoryLimit.NullUint64, + DiskInMB: cmd.DiskLimit.NullUint64, + LogRateLimitInBPS: types.NullInt(cmd.LogRateLimit), }) cmd.UI.DisplayWarnings(warnings) if err != nil { @@ -203,5 +206,5 @@ func shouldShowCurrentScale(err error) bool { } func (cmd ScaleCommand) shouldRestart() bool { - return cmd.DiskLimit.IsSet || cmd.MemoryLimit.IsSet + return cmd.DiskLimit.IsSet || cmd.MemoryLimit.IsSet || cmd.LogRateLimit.IsSet } diff --git a/command/v7/scale_command_test.go b/command/v7/scale_command_test.go index 0286c41ebfe..92638290018 100644 --- a/command/v7/scale_command_test.go +++ b/command/v7/scale_command_test.go @@ -154,55 +154,65 @@ var _ = Describe("scale Command", func() { ProcessSummaries: v7action.ProcessSummaries{ { Process: resources.Process{ - Type: constant.ProcessTypeWeb, - MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, - DiskInMB: types.NullUint64{Value: 1024, IsSet: true}, + Type: constant.ProcessTypeWeb, + MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, + DiskInMB: types.NullUint64{Value: 1024, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 1024 * 10, IsSet: true}, }, InstanceDetails: []v7action.ProcessInstance{ v7action.ProcessInstance{ - Index: 0, - State: constant.ProcessInstanceRunning, - MemoryUsage: 1000000, - DiskUsage: 1000000, - MemoryQuota: 33554432, - DiskQuota: 2000000, - Uptime: time.Since(time.Unix(267321600, 0)), + Index: 0, + State: constant.ProcessInstanceRunning, + MemoryUsage: 1000000, + DiskUsage: 1000000, + LogRate: 1024 * 5, + MemoryQuota: 33554432, + DiskQuota: 2000000, + LogRateLimit: 1024 * 10, + Uptime: time.Since(time.Unix(267321600, 0)), }, v7action.ProcessInstance{ - Index: 1, - State: constant.ProcessInstanceRunning, - MemoryUsage: 2000000, - DiskUsage: 2000000, - MemoryQuota: 33554432, - DiskQuota: 4000000, - Uptime: time.Since(time.Unix(330480000, 0)), + Index: 1, + State: constant.ProcessInstanceRunning, + MemoryUsage: 2000000, + DiskUsage: 2000000, + LogRate: 1024 * 3, + MemoryQuota: 33554432, + DiskQuota: 4000000, + LogRateLimit: 1024 * 10, + Uptime: time.Since(time.Unix(330480000, 0)), }, v7action.ProcessInstance{ - Index: 2, - State: constant.ProcessInstanceRunning, - MemoryUsage: 3000000, - DiskUsage: 3000000, - MemoryQuota: 33554432, - DiskQuota: 6000000, - Uptime: time.Since(time.Unix(1277164800, 0)), + Index: 2, + State: constant.ProcessInstanceRunning, + MemoryUsage: 3000000, + DiskUsage: 3000000, + LogRate: 1024 * 4, + MemoryQuota: 33554432, + DiskQuota: 6000000, + LogRateLimit: 1024 * 10, + Uptime: time.Since(time.Unix(1277164800, 0)), }, }, }, { Process: resources.Process{ - Type: "console", - MemoryInMB: types.NullUint64{Value: 16, IsSet: true}, - DiskInMB: types.NullUint64{Value: 512, IsSet: true}, + Type: "console", + MemoryInMB: types.NullUint64{Value: 16, IsSet: true}, + DiskInMB: types.NullUint64{Value: 512, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 1024 * 5, IsSet: true}, }, InstanceDetails: []v7action.ProcessInstance{ v7action.ProcessInstance{ - Index: 0, - State: constant.ProcessInstanceRunning, - MemoryUsage: 1000000, - DiskUsage: 1000000, - MemoryQuota: 33554432, - DiskQuota: 8000000, - Uptime: time.Since(time.Unix(167572800, 0)), + Index: 0, + State: constant.ProcessInstanceRunning, + MemoryUsage: 1000000, + DiskUsage: 1000000, + LogRate: 1024, + MemoryQuota: 33554432, + DiskQuota: 8000000, + LogRateLimit: 1024 * 5, + Uptime: time.Since(time.Unix(167572800, 0)), }, }, }, @@ -241,14 +251,17 @@ var _ = Describe("scale Command", func() { Expect(webProcessSummary.Instances[0].Memory).To(Equal("976.6K of 32M")) Expect(webProcessSummary.Instances[0].Disk).To(Equal("976.6K of 1.9M")) Expect(webProcessSummary.Instances[0].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[0].LogRate).To(Equal("5K/s of 10K/s")) Expect(webProcessSummary.Instances[1].Memory).To(Equal("1.9M of 32M")) Expect(webProcessSummary.Instances[1].Disk).To(Equal("1.9M of 3.8M")) Expect(webProcessSummary.Instances[1].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[1].LogRate).To(Equal("3K/s of 10K/s")) Expect(webProcessSummary.Instances[2].Memory).To(Equal("2.9M of 32M")) Expect(webProcessSummary.Instances[2].Disk).To(Equal("2.9M of 5.7M")) Expect(webProcessSummary.Instances[2].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[2].LogRate).To(Equal("4K/s of 10K/s")) consoleProcessSummary := firstAppTable.Processes[1] Expect(consoleProcessSummary.Type).To(Equal("console")) @@ -258,6 +271,7 @@ var _ = Describe("scale Command", func() { Expect(consoleProcessSummary.Instances[0].Memory).To(Equal("976.6K of 32M")) Expect(consoleProcessSummary.Instances[0].Disk).To(Equal("976.6K of 7.6M")) Expect(consoleProcessSummary.Instances[0].CPU).To(Equal("0.0%")) + Expect(consoleProcessSummary.Instances[0].LogRate).To(Equal("1K/s of 5K/s")) Expect(testUI.Err).To(Say("get-app-warning")) Expect(testUI.Err).To(Say("get-app-summary-warning")) @@ -292,6 +306,8 @@ var _ = Describe("scale Command", func() { cmd.DiskLimit.IsSet = true cmd.MemoryLimit.Value = 100 cmd.MemoryLimit.IsSet = true + cmd.LogRateLimit.Value = 1024 + cmd.LogRateLimit.IsSet = true fakeActor.ScaleProcessByApplicationReturns( v7action.Warnings{"scale-warning"}, nil) @@ -407,14 +423,17 @@ var _ = Describe("scale Command", func() { Expect(webProcessSummary.Instances[0].Memory).To(Equal("976.6K of 32M")) Expect(webProcessSummary.Instances[0].Disk).To(Equal("976.6K of 1.9M")) Expect(webProcessSummary.Instances[0].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[0].LogRate).To(Equal("5K/s of 10K/s")) Expect(webProcessSummary.Instances[1].Memory).To(Equal("1.9M of 32M")) Expect(webProcessSummary.Instances[1].Disk).To(Equal("1.9M of 3.8M")) Expect(webProcessSummary.Instances[1].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[1].LogRate).To(Equal("3K/s of 10K/s")) Expect(webProcessSummary.Instances[2].Memory).To(Equal("2.9M of 32M")) Expect(webProcessSummary.Instances[2].Disk).To(Equal("2.9M of 5.7M")) Expect(webProcessSummary.Instances[2].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[2].LogRate).To(Equal("4K/s of 10K/s")) consoleProcessSummary := firstAppTable.Processes[1] Expect(consoleProcessSummary.Type).To(Equal("console")) @@ -424,6 +443,7 @@ var _ = Describe("scale Command", func() { Expect(consoleProcessSummary.Instances[0].Memory).To(Equal("976.6K of 32M")) Expect(consoleProcessSummary.Instances[0].Disk).To(Equal("976.6K of 7.6M")) Expect(consoleProcessSummary.Instances[0].CPU).To(Equal("0.0%")) + Expect(consoleProcessSummary.Instances[0].LogRate).To(Equal("1K/s of 5K/s")) Expect(testUI.Err).To(Say("get-app-warning")) Expect(testUI.Err).To(Say("scale-warning")) @@ -440,10 +460,11 @@ var _ = Describe("scale Command", func() { appGUIDArg, scaleProcess := fakeActor.ScaleProcessByApplicationArgsForCall(0) Expect(appGUIDArg).To(Equal("some-app-guid")) Expect(scaleProcess).To(Equal(resources.Process{ - Type: constant.ProcessTypeWeb, - Instances: types.NullInt{Value: 2, IsSet: true}, - DiskInMB: types.NullUint64{Value: 50, IsSet: true}, - MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, + Type: constant.ProcessTypeWeb, + Instances: types.NullInt{Value: 2, IsSet: true}, + DiskInMB: types.NullUint64{Value: 50, IsSet: true}, + MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 1024, IsSet: true}, })) Expect(fakeActor.StopApplicationCallCount()).To(Equal(1)) @@ -752,6 +773,59 @@ var _ = Describe("scale Command", func() { }) }) + When("only the log rate limit option is provided", func() { + BeforeEach(func() { + cmd.LogRateLimit.Value = 2048 + cmd.LogRateLimit.IsSet = true + fakeActor.ScaleProcessByApplicationReturns( + v7action.Warnings{"scale-warning"}, + nil) + fakeActor.GetDetailedAppSummaryReturns( + appSummary, + v7action.Warnings{"get-instances-warning"}, + nil) + + _, err := input.Write([]byte("y\n")) + Expect(err).ToNot(HaveOccurred()) + }) + + It("scales, restarts, and displays scale properties", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(testUI.Out).To(Say("Scaling")) + Expect(testUI.Out).To(Say("This will cause the app to restart")) + Expect(testUI.Out).To(Say("Stopping")) + Expect(testUI.Out).To(Say("Starting")) + + Expect(testUI.Err).To(Say("get-app-warning")) + Expect(testUI.Err).To(Say("scale-warning")) + Expect(testUI.Err).To(Say("get-instances-warning")) + + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + appNameArg, spaceGUIDArg := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(appNameArg).To(Equal(app.Name)) + Expect(spaceGUIDArg).To(Equal("some-space-guid")) + + Expect(fakeActor.ScaleProcessByApplicationCallCount()).To(Equal(1)) + appGUIDArg, scaleProcess := fakeActor.ScaleProcessByApplicationArgsForCall(0) + Expect(appGUIDArg).To(Equal("some-app-guid")) + Expect(scaleProcess).To(Equal(resources.Process{ + Type: constant.ProcessTypeWeb, + LogRateLimitInBPS: types.NullInt{Value: 2048, IsSet: true}, + })) + + Expect(fakeActor.StopApplicationCallCount()).To(Equal(1)) + appGUID := fakeActor.StopApplicationArgsForCall(0) + Expect(appGUID).To(Equal("some-app-guid")) + + Expect(fakeActor.StartApplicationCallCount()).To(Equal(1)) + appGUID = fakeActor.StartApplicationArgsForCall(0) + Expect(appGUID).To(Equal("some-app-guid")) + + Expect(fakeActor.GetDetailedAppSummaryCallCount()).To(Equal(1)) + }) + }) + When("an error is encountered scaling the application", func() { var expectedErr error diff --git a/command/v7/shared/app_summary_displayer.go b/command/v7/shared/app_summary_displayer.go index 38978ee148d..e9c5b41a759 100644 --- a/command/v7/shared/app_summary_displayer.go +++ b/command/v7/shared/app_summary_displayer.go @@ -72,6 +72,14 @@ func routeSummary(rs []resources.Route) string { return strings.Join(formattedRoutes, ", ") } +func formatLogRateLimit(limit int64) string { + if limit == -1 { + return "unlimited" + } else { + return bytefmt.ByteSize(uint64(limit)) + "/s" + } +} + func (display AppSummaryDisplayer) displayAppInstancesTable(processSummary v7action.ProcessSummary) { table := [][]string{ { @@ -81,6 +89,7 @@ func (display AppSummaryDisplayer) displayAppInstancesTable(processSummary v7act display.UI.TranslateText("cpu"), display.UI.TranslateText("memory"), display.UI.TranslateText("disk"), + display.UI.TranslateText("logging"), display.UI.TranslateText("details"), }, } @@ -99,6 +108,10 @@ func (display AppSummaryDisplayer) displayAppInstancesTable(processSummary v7act "DiskUsage": bytefmt.ByteSize(instance.DiskUsage), "DiskQuota": bytefmt.ByteSize(instance.DiskQuota), }), + display.UI.TranslateText("{{.LogRate}}/s of {{.LogRateLimit}}", map[string]interface{}{ + "LogRate": bytefmt.ByteSize(instance.LogRate), + "LogRateLimit": formatLogRateLimit(instance.LogRateLimit), + }), instance.Details, }) } diff --git a/command/v7/shared/app_summary_displayer_test.go b/command/v7/shared/app_summary_displayer_test.go index d35d31af5cf..1ba958be041 100644 --- a/command/v7/shared/app_summary_displayer_test.go +++ b/command/v7/shared/app_summary_displayer_test.go @@ -16,6 +16,9 @@ import ( ) var _ = Describe("app summary displayer", func() { + + const instanceStatsTitles = `state\s+since\s+cpu\s+memory\s+disk\s+logging\s+details` + var ( appSummaryDisplayer *AppSummaryDisplayer output *Buffer @@ -54,59 +57,69 @@ var _ = Describe("app summary displayer", func() { ProcessSummaries: v7action.ProcessSummaries{ { Process: resources.Process{ - Type: constant.ProcessTypeWeb, - MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, - DiskInMB: types.NullUint64{Value: 1024, IsSet: true}, + Type: constant.ProcessTypeWeb, + MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, + DiskInMB: types.NullUint64{Value: 1024, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 1024 * 5, IsSet: true}, }, Sidecars: []resources.Sidecar{}, InstanceDetails: []v7action.ProcessInstance{ v7action.ProcessInstance{ - Index: 0, - State: constant.ProcessInstanceRunning, - MemoryUsage: 1000000, - DiskUsage: 1000000, - MemoryQuota: 33554432, - DiskQuota: 2000000, - Uptime: uptime, - Details: "Some Details 1", + Index: 0, + State: constant.ProcessInstanceRunning, + MemoryUsage: 1000000, + DiskUsage: 1000000, + LogRate: 1024, + MemoryQuota: 33554432, + DiskQuota: 2000000, + LogRateLimit: 1024 * 5, + Uptime: uptime, + Details: "Some Details 1", }, v7action.ProcessInstance{ - Index: 1, - State: constant.ProcessInstanceRunning, - MemoryUsage: 2000000, - DiskUsage: 2000000, - MemoryQuota: 33554432, - DiskQuota: 4000000, - Uptime: time.Since(time.Unix(330480000, 0)), - Details: "Some Details 2", + Index: 1, + State: constant.ProcessInstanceRunning, + MemoryUsage: 2000000, + DiskUsage: 2000000, + LogRate: 1024 * 2, + MemoryQuota: 33554432, + DiskQuota: 4000000, + LogRateLimit: 1024 * 5, + Uptime: time.Since(time.Unix(330480000, 0)), + Details: "Some Details 2", }, v7action.ProcessInstance{ - Index: 2, - State: constant.ProcessInstanceRunning, - MemoryUsage: 3000000, - DiskUsage: 3000000, - MemoryQuota: 33554432, - DiskQuota: 6000000, - Uptime: time.Since(time.Unix(1277164800, 0)), + Index: 2, + State: constant.ProcessInstanceRunning, + MemoryUsage: 3000000, + DiskUsage: 3000000, + LogRate: 1024 * 3, + MemoryQuota: 33554432, + DiskQuota: 6000000, + LogRateLimit: 1024 * 5, + Uptime: time.Since(time.Unix(1277164800, 0)), }, }, }, { Process: resources.Process{ - Type: "console", - MemoryInMB: types.NullUint64{Value: 16, IsSet: true}, - DiskInMB: types.NullUint64{Value: 512, IsSet: true}, + Type: "console", + MemoryInMB: types.NullUint64{Value: 16, IsSet: true}, + DiskInMB: types.NullUint64{Value: 512, IsSet: true}, + LogRateLimitInBPS: types.NullInt{Value: 256, IsSet: true}, }, Sidecars: []resources.Sidecar{}, InstanceDetails: []v7action.ProcessInstance{ v7action.ProcessInstance{ - Index: 0, - State: constant.ProcessInstanceRunning, - MemoryUsage: 1000000, - DiskUsage: 1000000, - MemoryQuota: 33554432, - DiskQuota: 8000000, - Uptime: time.Since(time.Unix(167572800, 0)), + Index: 0, + State: constant.ProcessInstanceRunning, + MemoryUsage: 1000000, + DiskUsage: 1000000, + LogRate: 128, + MemoryQuota: 33554432, + DiskQuota: 8000000, + LogRateLimit: 256, + Uptime: time.Since(time.Unix(167572800, 0)), }, }, }, @@ -130,16 +143,19 @@ var _ = Describe("app summary displayer", func() { Expect(time.Parse(time.RFC3339, webProcessSummary.Instances[0].Since)).To(BeTemporally("~", time.Now().Add(-uptime), 2*time.Second)) Expect(webProcessSummary.Instances[0].Disk).To(Equal("976.6K of 1.9M")) Expect(webProcessSummary.Instances[0].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[0].LogRate).To(Equal("1K/s of 5K/s")) Expect(webProcessSummary.Instances[0].Details).To(Equal("Some Details 1")) Expect(webProcessSummary.Instances[1].Memory).To(Equal("1.9M of 32M")) Expect(webProcessSummary.Instances[1].Disk).To(Equal("1.9M of 3.8M")) Expect(webProcessSummary.Instances[1].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[1].LogRate).To(Equal("2K/s of 5K/s")) Expect(webProcessSummary.Instances[1].Details).To(Equal("Some Details 2")) Expect(webProcessSummary.Instances[2].Memory).To(Equal("2.9M of 32M")) Expect(webProcessSummary.Instances[2].Disk).To(Equal("2.9M of 5.7M")) Expect(webProcessSummary.Instances[2].CPU).To(Equal("0.0%")) + Expect(webProcessSummary.Instances[2].LogRate).To(Equal("3K/s of 5K/s")) consoleProcessSummary := processTable.Processes[1] Expect(consoleProcessSummary.Type).To(Equal("console")) @@ -150,6 +166,45 @@ var _ = Describe("app summary displayer", func() { Expect(consoleProcessSummary.Instances[0].Memory).To(Equal("976.6K of 32M")) Expect(consoleProcessSummary.Instances[0].Disk).To(Equal("976.6K of 7.6M")) Expect(consoleProcessSummary.Instances[0].CPU).To(Equal("0.0%")) + Expect(consoleProcessSummary.Instances[0].LogRate).To(Equal("128B/s of 256B/s")) + }) + }) + + When("the log rate is unlimited", func() { + BeforeEach(func() { + summary = v7action.DetailedApplicationSummary{ + ApplicationSummary: v7action.ApplicationSummary{ + Application: resources.Application{ + GUID: "some-app-guid", + State: constant.ApplicationStarted, + }, + ProcessSummaries: v7action.ProcessSummaries{ + { + Process: resources.Process{ + Type: constant.ProcessTypeWeb, + MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, + DiskInMB: types.NullUint64{Value: 1024, IsSet: true}, + }, + Sidecars: []resources.Sidecar{}, + InstanceDetails: []v7action.ProcessInstance{ + v7action.ProcessInstance{ + Index: 0, + State: constant.ProcessInstanceRunning, + LogRate: 1024, + LogRateLimit: -1, + }, + }, + }, + }, + }, + } + }) + + It("renders unlimited log rate limits correctly", func() { + processTable := helpers.ParseV3AppProcessTable(output.Contents()) + webProcessSummary := processTable.Processes[0] + + Expect(webProcessSummary.Instances[0].LogRate).To(Equal("1K/s of unlimited")) }) }) @@ -197,7 +252,7 @@ var _ = Describe("app summary displayer", func() { It("lists instance stats for process types that have > 0 instances", func() { Expect(testUI.Out).To(Say(`type:\s+web`)) Expect(testUI.Out).To(Say(`sidecars: `)) - Expect(testUI.Out).To(Say(`state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Expect(testUI.Out).To(Say(instanceStatsTitles)) }) It("does not show the instance stats table for process types with 0 instances", func() { @@ -227,7 +282,7 @@ var _ = Describe("app summary displayer", func() { It("displays the instances table", func() { Expect(testUI.Out).To(Say(`type:\s+web`)) Expect(testUI.Out).To(Say(`sidecars: `)) - Expect(testUI.Out).To(Say(`state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Expect(testUI.Out).To(Say(instanceStatsTitles)) }) }) @@ -342,7 +397,7 @@ var _ = Describe("app summary displayer", func() { }) It("does not display the instance table", func() { - Expect(testUI.Out).NotTo(Say(`state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Expect(testUI.Out).NotTo(Say(instanceStatsTitles)) }) }) @@ -380,7 +435,7 @@ var _ = Describe("app summary displayer", func() { }) It("does not display the instance table", func() { - Expect(testUI.Out).NotTo(Say(`state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Expect(testUI.Out).NotTo(Say(instanceStatsTitles)) }) }) @@ -421,7 +476,7 @@ var _ = Describe("app summary displayer", func() { }) It("does not display the instance table", func() { - Expect(testUI.Out).NotTo(Say(`state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Expect(testUI.Out).NotTo(Say(instanceStatsTitles)) }) }) diff --git a/command/v7/shared/quota_displayer.go b/command/v7/shared/quota_displayer.go index d209223b18c..bcb84ddc973 100644 --- a/command/v7/shared/quota_displayer.go +++ b/command/v7/shared/quota_displayer.go @@ -12,7 +12,8 @@ import ( ) const ( - MEGABYTE = 1024 * 1024 + KILOBYTE = 1024 + MEGABYTE = 1024 * KILOBYTE GIGABYTE = 1024 * MEGABYTE TERABYTE = 1024 * GIGABYTE ) @@ -32,7 +33,7 @@ func (displayer QuotaDisplayer) DisplayQuotasTable(quotas []resources.Quota, emp } var keyValueTable = [][]string{ - {"name", "total memory", "instance memory", "routes", "service instances", "paid service plans", "app instances", "route ports"}, + {"name", "total memory", "instance memory", "routes", "service instances", "paid service plans", "app instances", "route ports", "log volume per second"}, } for _, quota := range quotas { @@ -45,6 +46,7 @@ func (displayer QuotaDisplayer) DisplayQuotasTable(quotas []resources.Quota, emp displayer.presentBooleanValue(*quota.Services.PaidServicePlans), displayer.presentQuotaValue(*quota.Apps.TotalAppInstances), displayer.presentQuotaValue(*quota.Routes.TotalReservedPorts), + displayer.presentQuotaBytesValue(*quota.Apps.TotalLogVolume), }) } @@ -60,6 +62,7 @@ func (displayer QuotaDisplayer) DisplaySingleQuota(quota resources.Quota) { {displayer.ui.TranslateText("paid service plans:"), displayer.presentBooleanValue(*quota.Services.PaidServicePlans)}, {displayer.ui.TranslateText("app instances:"), displayer.presentQuotaValue(*quota.Apps.TotalAppInstances)}, {displayer.ui.TranslateText("route ports:"), displayer.presentQuotaValue(*quota.Routes.TotalReservedPorts)}, + {displayer.ui.TranslateText("log volume per second:"), displayer.presentQuotaBytesValue(*quota.Apps.TotalLogVolume)}, } displayer.ui.DisplayKeyValueTable("", quotaTable, 3) @@ -81,6 +84,14 @@ func (displayer QuotaDisplayer) presentQuotaValue(limit types.NullInt) string { } } +func (displayer QuotaDisplayer) presentQuotaBytesValue(limit types.NullInt) string { + if !limit.IsSet { + return "unlimited" + } else { + return addMemoryUnits(float64(limit.Value)) + } +} + func (displayer QuotaDisplayer) presentQuotaMemoryValue(limit types.NullInt) string { if !limit.IsSet { return "unlimited" @@ -103,6 +114,11 @@ func addMemoryUnits(bytes float64) string { case bytes >= MEGABYTE: unit = "M" value /= MEGABYTE + case bytes >= KILOBYTE: + unit = "K" + value /= KILOBYTE + case bytes >= 1: + unit = "B" case bytes == 0: return "0" } diff --git a/command/v7/space_quota_command_test.go b/command/v7/space_quota_command_test.go index ce15a9c9fc4..20952d63888 100644 --- a/command/v7/space_quota_command_test.go +++ b/command/v7/space_quota_command_test.go @@ -102,6 +102,7 @@ var _ = Describe("Space Quota Command", func() { TotalMemory: &types.NullInt{IsSet: true, Value: 2048}, InstanceMemory: &types.NullInt{IsSet: true, Value: 1024}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 2}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 512}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{IsSet: false}, @@ -135,6 +136,7 @@ var _ = Describe("Space Quota Command", func() { Expect(testUI.Out).To(Say(`paid service plans:\s+disallowed`)) Expect(testUI.Out).To(Say(`app instances:\s+2`)) Expect(testUI.Out).To(Say(`route ports:\s+unlimited`)) + Expect(testUI.Out).To(Say(`log volume per second:\s+512B`)) }) }) }) diff --git a/command/v7/space_quotas_command_test.go b/command/v7/space_quotas_command_test.go index c0626293045..268b6dd9cc0 100644 --- a/command/v7/space_quotas_command_test.go +++ b/command/v7/space_quotas_command_test.go @@ -69,6 +69,7 @@ var _ = Describe("space-quotas command", func() { TotalMemory: &types.NullInt{Value: 1048576, IsSet: true}, InstanceMemory: &types.NullInt{Value: 32, IsSet: true}, TotalAppInstances: &types.NullInt{Value: 3, IsSet: true}, + TotalLogVolume: &types.NullInt{Value: 512, IsSet: true}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 3, IsSet: true}, @@ -98,8 +99,8 @@ var _ = Describe("space-quotas command", func() { orgGUID := fakeActor.GetSpaceQuotasByOrgGUIDArgsForCall(0) Expect(orgGUID).To(Equal(fakeConfig.TargetedOrganization().GUID)) - Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports`)) - Expect(testUI.Out).To(Say(`space-quota-1\s+1T\s+32M\s+5\s+3\s+allowed\s+3\s+2`)) + Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports\s+log volume per second`)) + Expect(testUI.Out).To(Say(`space-quota-1\s+1T\s+32M\s+5\s+3\s+allowed\s+3\s+2\s+512B`)) }) When("there are limits that have not been configured", func() { @@ -112,6 +113,7 @@ var _ = Describe("space-quotas command", func() { TotalMemory: &types.NullInt{Value: 0, IsSet: false}, InstanceMemory: &types.NullInt{Value: 0, IsSet: false}, TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, + TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, }, Services: resources.ServiceLimit{ TotalServiceInstances: &types.NullInt{Value: 0, IsSet: false}, @@ -130,8 +132,8 @@ var _ = Describe("space-quotas command", func() { It("should convert default values from the API into readable outputs", func() { Expect(executeErr).NotTo(HaveOccurred()) - Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports`)) - Expect(testUI.Out).To(Say(`default\s+unlimited\s+unlimited\s+unlimited\s+unlimited\s+allowed\s+unlimited\s+unlimited`)) + Expect(testUI.Out).To(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports\s+log volume per second`)) + Expect(testUI.Out).To(Say(`default\s+unlimited\s+unlimited\s+unlimited\s+unlimited\s+allowed\s+unlimited\s+unlimited\s+unlimited`)) }) }) diff --git a/command/v7/update_org_quota_command.go b/command/v7/update_org_quota_command.go index 04a93c6a7b8..70124cfc201 100644 --- a/command/v7/update_org_quota_command.go +++ b/command/v7/update_org_quota_command.go @@ -9,18 +9,20 @@ import ( type UpdateOrgQuotaCommand struct { BaseCommand - RequiredArgs flag.OrganizationQuota `positional-args:"Yes"` - NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. -1 represents an unlimited amount."` - PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans."` - NoPaidServicePlans bool `long:"disallow-paid-service-plans" description:"Disallow provisioning instances of paid service plans."` - PerProcessMemory flag.MemoryWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` - TotalMemory flag.MemoryWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` - NewName string `short:"n" description:"New name"` - TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount."` - TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount."` - TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount."` - usage interface{} `usage:"CF_NAME update-org-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-n NEW_NAME] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans | --disallow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS]"` - relatedCommands interface{} `related_commands:"org, org-quota"` + RequiredArgs flag.OrganizationQuota `positional-args:"Yes"` + NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. -1 represents an unlimited amount."` + PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans."` + NoPaidServicePlans bool `long:"disallow-paid-service-plans" description:"Disallow provisioning instances of paid service plans."` + PerProcessMemory flag.MegabytesWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` + TotalMemory flag.MegabytesWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` + NewName string `short:"n" description:"New name"` + TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount."` + TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount."` + TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount."` + TotalLogVolume flag.BytesWithUnlimited `short:"l" description:"Total log volume per second all processes can have, in bytes (e.g. 128B, 4K, 1M). -1 represents an unlimited amount."` + + usage interface{} `usage:"CF_NAME update-org-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-n NEW_NAME] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans | --disallow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS] [-l LOG_VOLUME]"` + relatedCommands interface{} `related_commands:"org, org-quota"` } func (cmd UpdateOrgQuotaCommand) Execute(args []string) error { @@ -61,6 +63,7 @@ func (cmd UpdateOrgQuotaCommand) Execute(args []string) error { TotalServiceInstances: convertIntegerLimitFlagToNullInt(cmd.TotalServiceInstances), TotalRoutes: convertIntegerLimitFlagToNullInt(cmd.TotalRoutes), TotalReservedPorts: convertIntegerLimitFlagToNullInt(cmd.TotalReservedPorts), + TotalLogVolume: convertBytesFlagToNullInt(cmd.TotalLogVolume), } warnings, err := cmd.Actor.UpdateOrganizationQuota(oldQuotaName, cmd.NewName, updatedQuotaLimits) diff --git a/command/v7/update_org_quota_command_test.go b/command/v7/update_org_quota_command_test.go index 4dd534d5dc2..d5b9e2253d9 100644 --- a/command/v7/update_org_quota_command_test.go +++ b/command/v7/update_org_quota_command_test.go @@ -91,11 +91,12 @@ var _ = Describe("UpdateOrgQuotaCommand", func() { cmd.NewName = "new-org-quota-name" cmd.PaidServicePlans = true cmd.NumAppInstances = flag.IntegerLimit{IsSet: true, Value: 10} - cmd.PerProcessMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 9} - cmd.TotalMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 2048} + cmd.PerProcessMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 9} + cmd.TotalMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 2048} cmd.TotalRoutes = flag.IntegerLimit{IsSet: true, Value: 7} cmd.TotalReservedPorts = flag.IntegerLimit{IsSet: true, Value: 1} cmd.TotalServiceInstances = flag.IntegerLimit{IsSet: true, Value: 2} + cmd.TotalLogVolume = flag.BytesWithUnlimited{IsSet: true, Value: 8} fakeActor.UpdateOrganizationQuotaReturns( v7action.Warnings{"warning"}, nil) @@ -130,6 +131,9 @@ var _ = Describe("UpdateOrgQuotaCommand", func() { Expect(quotaLimits.TotalServiceInstances.IsSet).To(Equal(true)) Expect(quotaLimits.TotalServiceInstances.Value).To(Equal(2)) + Expect(quotaLimits.TotalLogVolume.IsSet).To(Equal(true)) + Expect(quotaLimits.TotalLogVolume.Value).To(Equal(8)) + Expect(testUI.Out).To(Say("Updating org quota %s as bob...", orgQuotaName)) Expect(testUI.Out).To(Say("OK")) }) @@ -137,7 +141,7 @@ var _ = Describe("UpdateOrgQuotaCommand", func() { When("only some org quota limits are updated", func() { BeforeEach(func() { - cmd.TotalMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 2048} + cmd.TotalMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 2048} cmd.TotalServiceInstances = flag.IntegerLimit{IsSet: true, Value: 2} fakeActor.UpdateOrganizationQuotaReturns( v7action.Warnings{"warning"}, @@ -169,6 +173,8 @@ var _ = Describe("UpdateOrgQuotaCommand", func() { Expect(quotaLimits.TotalReservedPorts).To(BeNil()) + Expect(quotaLimits.TotalLogVolume).To(BeNil()) + Expect(testUI.Out).To(Say("Updating org quota %s as bob...", orgQuotaName)) Expect(testUI.Out).To(Say("OK")) }) diff --git a/command/v7/update_space_quota_command.go b/command/v7/update_space_quota_command.go index c2905ecae64..300645c44eb 100644 --- a/command/v7/update_space_quota_command.go +++ b/command/v7/update_space_quota_command.go @@ -9,18 +9,20 @@ import ( type UpdateSpaceQuotaCommand struct { BaseCommand - RequiredArgs flag.SpaceQuota `positional-args:"Yes"` - NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. -1 represents an unlimited amount."` - PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans."` - NoPaidServicePlans bool `long:"disallow-paid-service-plans" description:"Disallow provisioning instances of paid service plans."` - PerProcessMemory flag.MemoryWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` - TotalMemory flag.MemoryWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` - NewName string `short:"n" description:"New name"` - TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount."` - TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount."` - TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount."` - usage interface{} `usage:"CF_NAME update-space-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-n NEW_NAME] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans | --disallow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS]"` - relatedCommands interface{} `related_commands:"space, space-quota, space-quotas"` + RequiredArgs flag.SpaceQuota `positional-args:"Yes"` + NumAppInstances flag.IntegerLimit `short:"a" description:"Total number of application instances. -1 represents an unlimited amount."` + PaidServicePlans bool `long:"allow-paid-service-plans" description:"Allow provisioning instances of paid service plans."` + NoPaidServicePlans bool `long:"disallow-paid-service-plans" description:"Disallow provisioning instances of paid service plans."` + PerProcessMemory flag.MegabytesWithUnlimited `short:"i" description:"Maximum amount of memory a process can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` + TotalMemory flag.MegabytesWithUnlimited `short:"m" description:"Total amount of memory all processes can have (e.g. 1024M, 1G, 10G). -1 represents an unlimited amount."` + NewName string `short:"n" description:"New name"` + TotalRoutes flag.IntegerLimit `short:"r" description:"Total number of routes. -1 represents an unlimited amount."` + TotalReservedPorts flag.IntegerLimit `long:"reserved-route-ports" description:"Maximum number of routes that may be created with ports. -1 represents an unlimited amount."` + TotalServiceInstances flag.IntegerLimit `short:"s" description:"Total number of service instances. -1 represents an unlimited amount."` + TotalLogVolume flag.BytesWithUnlimited `short:"l" description:"Total log volume per second all processes can have, in bytes (e.g. 128B, 4K, 1M). -1 represents an unlimited amount."` + + usage interface{} `usage:"CF_NAME update-space-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] [-n NEW_NAME] [-r ROUTES] [-s SERVICE_INSTANCES] [-a APP_INSTANCES] [--allow-paid-service-plans | --disallow-paid-service-plans] [--reserved-route-ports RESERVED_ROUTE_PORTS] [-l LOG_VOLUME]"` + relatedCommands interface{} `related_commands:"space, space-quota, space-quotas"` } func (cmd UpdateSpaceQuotaCommand) Execute(args []string) error { @@ -63,6 +65,7 @@ func (cmd UpdateSpaceQuotaCommand) Execute(args []string) error { TotalServiceInstances: convertIntegerLimitFlagToNullInt(cmd.TotalServiceInstances), TotalRoutes: convertIntegerLimitFlagToNullInt(cmd.TotalRoutes), TotalReservedPorts: convertIntegerLimitFlagToNullInt(cmd.TotalReservedPorts), + TotalLogVolume: convertBytesFlagToNullInt(cmd.TotalLogVolume), } warnings, err := cmd.Actor.UpdateSpaceQuota(oldQuotaName, orgGUID, cmd.NewName, updatedQuotaLimits) diff --git a/command/v7/update_space_quota_command_test.go b/command/v7/update_space_quota_command_test.go index 2caf0945a62..42a7b8079b3 100644 --- a/command/v7/update_space_quota_command_test.go +++ b/command/v7/update_space_quota_command_test.go @@ -94,11 +94,12 @@ var _ = Describe("UpdateSpaceQuotaCommand", func() { cmd.NewName = "new-space-quota-name" cmd.PaidServicePlans = true cmd.NumAppInstances = flag.IntegerLimit{IsSet: true, Value: 10} - cmd.PerProcessMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 9} - cmd.TotalMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 2048} + cmd.PerProcessMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 9} + cmd.TotalMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 2048} cmd.TotalRoutes = flag.IntegerLimit{IsSet: true, Value: 7} cmd.TotalReservedPorts = flag.IntegerLimit{IsSet: true, Value: 1} cmd.TotalServiceInstances = flag.IntegerLimit{IsSet: true, Value: 2} + cmd.TotalLogVolume = flag.BytesWithUnlimited{IsSet: true, Value: 512} fakeActor.UpdateSpaceQuotaReturns( v7action.Warnings{"warning"}, nil) @@ -134,6 +135,9 @@ var _ = Describe("UpdateSpaceQuotaCommand", func() { Expect(quotaLimits.TotalServiceInstances.IsSet).To(Equal(true)) Expect(quotaLimits.TotalServiceInstances.Value).To(Equal(2)) + Expect(quotaLimits.TotalLogVolume.IsSet).To(Equal(true)) + Expect(quotaLimits.TotalLogVolume.Value).To(Equal(512)) + Expect(testUI.Out).To(Say("Updating space quota %s for org %s as bob...", spaceQuotaName, orgName)) Expect(testUI.Out).To(Say("OK")) }) @@ -141,7 +145,7 @@ var _ = Describe("UpdateSpaceQuotaCommand", func() { When("only some org quota limits are updated", func() { BeforeEach(func() { - cmd.TotalMemory = flag.MemoryWithUnlimited{IsSet: true, Value: 2048} + cmd.TotalMemory = flag.MegabytesWithUnlimited{IsSet: true, Value: 2048} cmd.TotalServiceInstances = flag.IntegerLimit{IsSet: true, Value: 2} fakeActor.UpdateSpaceQuotaReturns( v7action.Warnings{"warning"}, @@ -174,6 +178,8 @@ var _ = Describe("UpdateSpaceQuotaCommand", func() { Expect(quotaLimits.TotalReservedPorts).To(BeNil()) + Expect(quotaLimits.TotalLogVolume).To(BeNil()) + Expect(testUI.Out).To(Say("Updating space quota %s for org %s as bob...", spaceQuotaName, orgName)) Expect(testUI.Out).To(Say("OK")) }) diff --git a/integration/helpers/app.go b/integration/helpers/app.go index 92cccfa3902..963fa464d31 100644 --- a/integration/helpers/app.go +++ b/integration/helpers/app.go @@ -369,3 +369,17 @@ func WaitForAppDiskToTakeEffect(appName string, processIndex int, instanceIndex return appTable.Processes[processIndex].Instances[instanceIndex].Disk }).Should(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]? of %s`, expectedDisk))) } + +func WaitForLogRateLimitToTakeEffect(appName string, processIndex int, instanceIndex int, shouldRestartFirst bool, expectedLogRateLimit string) { + if shouldRestartFirst { + session := CF("restart", appName) + Eventually(session).Should(Exit(0)) + } + + Eventually(func() string { + session := CF("app", appName) + Eventually(session).Should(Exit(0)) + appTable := ParseV3AppProcessTable(session.Out.Contents()) + return appTable.Processes[processIndex].Instances[instanceIndex].LogRate + }).Should(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]?/s of %s/s`, expectedLogRateLimit))) +} diff --git a/integration/helpers/app_instance_table.go b/integration/helpers/app_instance_table.go index 440d8b91bcf..57964f11261 100644 --- a/integration/helpers/app_instance_table.go +++ b/integration/helpers/app_instance_table.go @@ -14,6 +14,7 @@ type AppInstanceRow struct { CPU string Memory string Disk string + LogRate string Details string } @@ -55,11 +56,13 @@ func ParseV3AppProcessTable(input []byte) AppTable { switch { case strings.HasPrefix(row, "#"): + const columnCount = 8 + // instance row columns := splitColumns(row) details := "" - if len(columns) >= 7 { - details = columns[6] + if len(columns) >= columnCount { + details = columns[7] } instanceRow := AppInstanceRow{ @@ -69,6 +72,7 @@ func ParseV3AppProcessTable(input []byte) AppTable { CPU: columns[3], Memory: columns[4], Disk: columns[5], + LogRate: columns[6], Details: details, } lastProcessIndex := len(appTable.Processes) - 1 diff --git a/integration/helpers/app_instance_table_test.go b/integration/helpers/app_instance_table_test.go index 31fc921e530..6ae03a086a4 100644 --- a/integration/helpers/app_instance_table_test.go +++ b/integration/helpers/app_instance_table_test.go @@ -21,17 +21,17 @@ buildpacks: ruby 1.6.44 type: web instances: 4/4 memory usage: 32M - state since cpu memory disk -#0 running 2017-08-02 17:12:10 PM 0.0% 21.2M of 32M 84.5M of 1G -#1 running 2017-08-03 09:39:25 AM 0.2% 19.3M of 32M 84.5M of 1G -#2 running 2017-08-03 03:29:25 AM 0.1% 22.8M of 32M 84.5M of 1G -#3 running 2017-08-02 17:12:10 PM 0.2% 22.9M of 32M 84.5M of 1G + state since cpu memory disk logging +#0 running 2017-08-02 17:12:10 PM 0.0% 21.2M of 32M 84.5M of 1G 5B/s of 1K/s +#1 running 2017-08-03 09:39:25 AM 0.2% 19.3M of 32M 84.5M of 1G 7B/s of 1K/s +#2 running 2017-08-03 03:29:25 AM 0.1% 22.8M of 32M 84.5M of 1G 10B/s of 1K/s +#3 running 2017-08-02 17:12:10 PM 0.2% 22.9M of 32M 84.5M of 1G 8B/s of 1K/s type: worker instances: 1/1 memory usage: 32M - state since cpu memory disk -#0 stopped 2017-08-02 17:12:10 PM 0.0% 0M of 32M 0M of 1G + state since cpu memory disk logging +#0 stopped 2017-08-02 17:12:10 PM 0.0% 0M of 32M 0M of 1G 0B/s of 1K/s ` appInstanceTable := ParseV3AppProcessTable([]byte(input)) Expect(appInstanceTable).To(Equal(AppTable{ @@ -41,10 +41,10 @@ memory usage: 32M InstanceCount: "4/4", MemUsage: "32M", Instances: []AppInstanceRow{ - {Index: "#0", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.0%", Memory: "21.2M of 32M", Disk: "84.5M of 1G"}, - {Index: "#1", State: "running", Since: "2017-08-03 09:39:25 AM", CPU: "0.2%", Memory: "19.3M of 32M", Disk: "84.5M of 1G"}, - {Index: "#2", State: "running", Since: "2017-08-03 03:29:25 AM", CPU: "0.1%", Memory: "22.8M of 32M", Disk: "84.5M of 1G"}, - {Index: "#3", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.2%", Memory: "22.9M of 32M", Disk: "84.5M of 1G"}, + {Index: "#0", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.0%", Memory: "21.2M of 32M", Disk: "84.5M of 1G", LogRate: "5B/s of 1K/s"}, + {Index: "#1", State: "running", Since: "2017-08-03 09:39:25 AM", CPU: "0.2%", Memory: "19.3M of 32M", Disk: "84.5M of 1G", LogRate: "7B/s of 1K/s"}, + {Index: "#2", State: "running", Since: "2017-08-03 03:29:25 AM", CPU: "0.1%", Memory: "22.8M of 32M", Disk: "84.5M of 1G", LogRate: "10B/s of 1K/s"}, + {Index: "#3", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.2%", Memory: "22.9M of 32M", Disk: "84.5M of 1G", LogRate: "8B/s of 1K/s"}, }, }, { @@ -52,7 +52,7 @@ memory usage: 32M InstanceCount: "1/1", MemUsage: "32M", Instances: []AppInstanceRow{ - {Index: "#0", State: "stopped", Since: "2017-08-02 17:12:10 PM", CPU: "0.0%", Memory: "0M of 32M", Disk: "0M of 1G"}, + {Index: "#0", State: "stopped", Since: "2017-08-02 17:12:10 PM", CPU: "0.0%", Memory: "0M of 32M", Disk: "0M of 1G", LogRate: "0B/s of 1K/s"}, }, }, }, @@ -66,11 +66,11 @@ Showing health and status for app dora in org wut / space wut as admin... type: web instances: 4/4 memory usage: 32M - state since cpu memory disk -#0 running 2017-08-02 17:12:10 PM 0.0% 21.2M of 32M 84.5M of 1G -#1 running 2017-08-03 09:39:25 AM 0.2% 19.3M of 32M 84.5M of 1G -#2 running 2017-08-03 03:29:25 AM 0.1% 22.8M of 32M 84.5M of 1G -#3 running 2017-08-02 17:12:10 PM 0.2% 22.9M of 32M 84.5M of 1G + state since cpu memory disk logging +#0 running 2017-08-02 17:12:10 PM 0.0% 21.2M of 32M 84.5M of 1G 1.3K/s of 5K/s +#1 running 2017-08-03 09:39:25 AM 0.2% 19.3M of 32M 84.5M of 1G 1.2K/s of 5K/s +#2 running 2017-08-03 03:29:25 AM 0.1% 22.8M of 32M 84.5M of 1G 1.1K/s of 5K/s +#3 running 2017-08-02 17:12:10 PM 0.2% 22.9M of 32M 84.5M of 1G 1.2K/s of 5K/s ` appInstanceTable := ParseV3AppProcessTable([]byte(input)) Expect(appInstanceTable).To(Equal(AppTable{ @@ -80,10 +80,10 @@ memory usage: 32M InstanceCount: "4/4", MemUsage: "32M", Instances: []AppInstanceRow{ - {Index: "#0", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.0%", Memory: "21.2M of 32M", Disk: "84.5M of 1G"}, - {Index: "#1", State: "running", Since: "2017-08-03 09:39:25 AM", CPU: "0.2%", Memory: "19.3M of 32M", Disk: "84.5M of 1G"}, - {Index: "#2", State: "running", Since: "2017-08-03 03:29:25 AM", CPU: "0.1%", Memory: "22.8M of 32M", Disk: "84.5M of 1G"}, - {Index: "#3", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.2%", Memory: "22.9M of 32M", Disk: "84.5M of 1G"}, + {Index: "#0", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.0%", Memory: "21.2M of 32M", Disk: "84.5M of 1G", LogRate: "1.3K/s of 5K/s"}, + {Index: "#1", State: "running", Since: "2017-08-03 09:39:25 AM", CPU: "0.2%", Memory: "19.3M of 32M", Disk: "84.5M of 1G", LogRate: "1.2K/s of 5K/s"}, + {Index: "#2", State: "running", Since: "2017-08-03 03:29:25 AM", CPU: "0.1%", Memory: "22.8M of 32M", Disk: "84.5M of 1G", LogRate: "1.1K/s of 5K/s"}, + {Index: "#3", State: "running", Since: "2017-08-02 17:12:10 PM", CPU: "0.2%", Memory: "22.9M of 32M", Disk: "84.5M of 1G", LogRate: "1.2K/s of 5K/s"}, }, }, }, diff --git a/integration/v7/global/org_quota_command_test.go b/integration/v7/global/org_quota_command_test.go index 7161c979982..ccd33cde2ba 100644 --- a/integration/v7/global/org_quota_command_test.go +++ b/integration/v7/global/org_quota_command_test.go @@ -70,6 +70,7 @@ var _ = Describe("org-quota command", func() { Eventually(session).Should(Say(`paid service plans:\s+allowed`)) Eventually(session).Should(Say(`app instances:\s+unlimited`)) Eventually(session).Should(Say(`route ports:\s+100`)) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) Eventually(session).Should(Exit(0)) }) diff --git a/integration/v7/global/org_quotas_command_test.go b/integration/v7/global/org_quotas_command_test.go index f264c4ea438..4327504b7b5 100644 --- a/integration/v7/global/org_quotas_command_test.go +++ b/integration/v7/global/org_quotas_command_test.go @@ -60,8 +60,8 @@ var _ = Describe("org-quotas command", func() { It("lists the org quotas", func() { session := helpers.CF("org-quotas") - Eventually(session).Should(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports`)) - Eventually(session).Should(Say(`%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s`, quotaName, totalMemory, instanceMemory, routes, serviceInstances, "allowed", appInstances, reservedRoutePorts)) + Eventually(session).Should(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports\s+log volume per second`)) + Eventually(session).Should(Say(`%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s`, quotaName, totalMemory, instanceMemory, routes, serviceInstances, "allowed", appInstances, reservedRoutePorts, "unlimited")) Eventually(session).Should(Exit(0)) }) }) diff --git a/integration/v7/isolated/app_command_test.go b/integration/v7/isolated/app_command_test.go index 7eed60c44ce..4c91a401b56 100644 --- a/integration/v7/isolated/app_command_test.go +++ b/integration/v7/isolated/app_command_test.go @@ -131,7 +131,7 @@ applications: Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+\d/2`)) Eventually(session).Should(Say(`memory usage:\s+128M`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+logging\s+details`)) Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) Eventually(session).Should(Exit(0)) diff --git a/integration/v7/isolated/create_org_quota_command_test.go b/integration/v7/isolated/create_org_quota_command_test.go index d9c858d695f..45c05873888 100644 --- a/integration/v7/isolated/create_org_quota_command_test.go +++ b/integration/v7/isolated/create_org_quota_command_test.go @@ -1,6 +1,7 @@ package isolated import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" "code.cloudfoundry.org/cli/integration/helpers" . "github.com/onsi/ginkgo" @@ -32,7 +33,7 @@ var _ = Describe("create-org-quota command", func() { Eventually(session).Should(Say("NAME:")) Eventually(session).Should(Say("create-org-quota - Define a new quota for an organization")) Eventually(session).Should(Say("USAGE:")) - Eventually(session).Should(Say(`cf create-org-quota ORG_QUOTA \[-m TOTAL_MEMORY\] \[-i INSTANCE_MEMORY\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\]`)) + Eventually(session).Should(Say(`cf create-org-quota ORG_QUOTA \[-m TOTAL_MEMORY\] \[-i INSTANCE_MEMORY\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\] \[-l LOG_VOLUME\]`)) Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("create-quota")) Eventually(session).Should(Say("OPTIONS:")) @@ -43,6 +44,7 @@ var _ = Describe("create-org-quota command", func() { Eventually(session).Should(Say(`-r\s+Total number of routes. -1 represents an unlimited amount. \(Default: 0\)`)) Eventually(session).Should(Say(`--reserved-route-ports\s+Maximum number of routes that may be created with ports. -1 represents an unlimited amount. \(Default: 0\)`)) Eventually(session).Should(Say(`-s\s+Total number of service instances. -1 represents an unlimited amount. \(Default: 0\)`)) + Eventually(session).Should(Say(`-l\s+Total log volume per second all processes can have, in bytes \(e.g. 128B, 4K, 1M\). -1 represents an unlimited amount. \(Default: -1\)`)) Eventually(session).Should(Say("SEE ALSO:")) Eventually(session).Should(Say("create-org, org-quotas, set-org-quota")) Eventually(session).Should(Exit(0)) @@ -119,7 +121,8 @@ var _ = Describe("create-org-quota command", func() { "-m", "4M", "-r", "6", "--reserved-route-ports", "5", - "-s", "7") + "-s", "7", + ) Eventually(session).Should(Say("Creating org quota %s as %s...", orgQuotaName, userName)) Eventually(session).Should(Say("OK")) Eventually(session).Should(Exit(0)) @@ -133,8 +136,30 @@ var _ = Describe("create-org-quota command", func() { Eventually(session).Should(Say(`paid service plans:\s+allowed`)) Eventually(session).Should(Say(`app instances:\s+2`)) Eventually(session).Should(Say(`route ports:\s+5`)) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) Eventually(session).Should(Exit(0)) }) + + When("CAPI supports log rate limits", func() { + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + }) + + It("creates the quota with the specified values", func() { + + userName, _ := helpers.GetCredentials() + session := helpers.CF("create-org-quota", orgQuotaName, + "-l", "32K", + ) + Eventually(session).Should(Say("Creating org quota %s as %s...", orgQuotaName, userName)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Exit(0)) + + session = helpers.CF("org-quota", orgQuotaName) + Eventually(session).Should(Say(`log volume per second:\s+32K`)) + Eventually(session).Should(Exit(0)) + }) + }) }) When("The flags are all set to -1", func() { @@ -146,7 +171,8 @@ var _ = Describe("create-org-quota command", func() { "-m", "-1", "-r", "-1", "-s", "-1", - "--reserved-route-ports", "-1") + "--reserved-route-ports", "-1", + ) Eventually(session).Should(Say("Creating org quota %s as %s...", orgQuotaName, userName)) Eventually(session).Should(Say("OK")) Eventually(session).Should(Exit(0)) @@ -158,8 +184,29 @@ var _ = Describe("create-org-quota command", func() { Eventually(session).Should(Say(`service instances:\s+unlimited`)) Eventually(session).Should(Say(`app instances:\s+unlimited`)) Eventually(session).Should(Say(`route ports:\s+unlimited`)) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) Eventually(session).Should(Exit(0)) }) + + When("CAPI supports log rate limits", func() { + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + }) + + It("creates the quota with the specified values", func() { + userName, _ := helpers.GetCredentials() + session := helpers.CF("create-org-quota", orgQuotaName, + "-l", "-1", + ) + Eventually(session).Should(Say("Creating org quota %s as %s...", orgQuotaName, userName)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Exit(0)) + + session = helpers.CF("org-quota", orgQuotaName) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) + Eventually(session).Should(Exit(0)) + }) + }) }) }) diff --git a/integration/v7/isolated/create_space_quota_command_test.go b/integration/v7/isolated/create_space_quota_command_test.go index 969a2a540dd..ae73c5aa8e2 100644 --- a/integration/v7/isolated/create_space_quota_command_test.go +++ b/integration/v7/isolated/create_space_quota_command_test.go @@ -1,6 +1,7 @@ package isolated import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" "code.cloudfoundry.org/cli/integration/helpers" . "github.com/onsi/ginkgo" @@ -29,7 +30,7 @@ var _ = Describe("create-space-quota command", func() { Eventually(session).Should(Say("NAME:")) Eventually(session).Should(Say("create-space-quota - Define a new quota for a space")) Eventually(session).Should(Say("USAGE:")) - Eventually(session).Should(Say(`cf create-space-quota QUOTA \[-m TOTAL_MEMORY\] \[-i INSTANCE_MEMORY\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\]`)) + Eventually(session).Should(Say(`cf create-space-quota QUOTA \[-m TOTAL_MEMORY\] \[-i INSTANCE_MEMORY\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\] \[-l LOG_VOLUME\]`)) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say(`-a\s+Total number of application instances. \(Default: unlimited\)`)) Eventually(session).Should(Say(`--allow-paid-service-plans\s+Allow provisioning instances of paid service plans. \(Default: disallowed\)`)) @@ -38,6 +39,7 @@ var _ = Describe("create-space-quota command", func() { Eventually(session).Should(Say(`-r\s+Total number of routes. -1 represents an unlimited amount. \(Default: 0\)`)) Eventually(session).Should(Say(`--reserved-route-ports\s+Maximum number of routes that may be created with ports. -1 represents an unlimited amount. \(Default: 0\)`)) Eventually(session).Should(Say(`-s\s+Total number of service instances. -1 represents an unlimited amount. \(Default: 0\)`)) + Eventually(session).Should(Say(`-l\s+Total log volume per second all processes can have, in bytes \(e.g. 128B, 4K, 1M\). -1 represents an unlimited amount. \(Default: -1\).`)) Eventually(session).Should(Say("SEE ALSO:")) Eventually(session).Should(Say("create-space, set-space-quota, space-quotas")) Eventually(session).Should(Exit(0)) @@ -95,6 +97,7 @@ var _ = Describe("create-space-quota command", func() { Eventually(session).Should(Say(`paid service plans:\s+disallowed`)) Eventually(session).Should(Say(`app instances:\s+unlimited`)) Eventually(session).Should(Say(`route ports:\s+0`)) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) Eventually(session).Should(Exit(0)) }) }) @@ -122,8 +125,26 @@ var _ = Describe("create-space-quota command", func() { Eventually(session).Should(Say(`paid service plans:\s+allowed`)) Eventually(session).Should(Say(`app instances:\s+2`)) Eventually(session).Should(Say(`route ports:\s+6`)) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) Eventually(session).Should(Exit(0)) }) + + When("CAPI supports log rate limits", func() { + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + }) + + It("creates the quota with the specified values", func() { + session := helpers.CF("create-space-quota", spaceQuotaName, "-l", "2K") + Eventually(session).Should(Say("Creating space quota %s for org %s as %s...", spaceQuotaName, orgName, userName)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Exit(0)) + + session = helpers.CF("space-quota", spaceQuotaName) + Eventually(session).Should(Say(`log volume per second:\s+2K`)) + Eventually(session).Should(Exit(0)) + }) + }) }) }) diff --git a/integration/v7/isolated/restart_command_test.go b/integration/v7/isolated/restart_command_test.go index 42bbd23c7a2..64bdf0cb274 100644 --- a/integration/v7/isolated/restart_command_test.go +++ b/integration/v7/isolated/restart_command_test.go @@ -12,6 +12,12 @@ import ( ) var _ = Describe("restart command", func() { + + const ( + instanceStatsTitles = `\s+state\s+since\s+cpu\s+memory\s+disk\s+logging\s+details` + instanceStatsValues = `#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z` + ) + var ( orgName string spaceName string @@ -105,8 +111,8 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) - Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) + Eventually(session).Should(Say(instanceStatsTitles)) + Eventually(session).Should(Say(instanceStatsValues)) }) }) @@ -128,8 +134,8 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) - Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) + Eventually(session).Should(Say(instanceStatsTitles)) + Eventually(session).Should(Say(instanceStatsValues)) Expect(session.Out.Contents()).NotTo(ContainSubstring("Staging app and tracing logs...")) @@ -165,8 +171,8 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) - Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) + Eventually(session).Should(Say(instanceStatsTitles)) + Eventually(session).Should(Say(instanceStatsValues)) Eventually(session).Should(Exit(0)) Expect(session.Err).ToNot(Say(`timeout connecting to log server, no log will be shown`)) @@ -193,8 +199,8 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) - Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) + Eventually(session).Should(Say(instanceStatsTitles)) + Eventually(session).Should(Say(instanceStatsValues)) Expect(session.Out.Contents()).NotTo(ContainSubstring("Staging app and tracing logs...")) Expect(session.Out.Contents()).NotTo(ContainSubstring("Stopping app...")) @@ -222,8 +228,8 @@ var _ = Describe("restart command", func() { Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) - Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) + Eventually(session).Should(Say(instanceStatsTitles)) + Eventually(session).Should(Say(instanceStatsValues)) Expect(session.Out.Contents()).NotTo(ContainSubstring("Stopping app...")) diff --git a/integration/v7/isolated/run_task_command_test.go b/integration/v7/isolated/run_task_command_test.go index 05ec1f3d159..3b3c7167f16 100644 --- a/integration/v7/isolated/run_task_command_test.go +++ b/integration/v7/isolated/run_task_command_test.go @@ -4,6 +4,7 @@ import ( "fmt" "path" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/integration/helpers" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -19,7 +20,7 @@ var _ = Describe("run-task command", func() { Expect(session).To(Say("NAME:")) Expect(session).To(Say(" run-task - Run a one-off task on an app")) Expect(session).To(Say("USAGE:")) - Expect(session).To(Say(` cf run-task APP_NAME \[--command COMMAND\] \[-k DISK] \[-m MEMORY\] \[--name TASK_NAME\] \[--process PROCESS_TYPE\]`)) + Expect(session).To(Say(` cf run-task APP_NAME \[--command COMMAND\] \[-k DISK] \[-m MEMORY\] \[-l LOG_RATE_LIMIT\] \[--name TASK_NAME\] \[--process PROCESS_TYPE\]`)) Expect(session).To(Say("TIP:")) Expect(session).To(Say(" Use 'cf logs' to display the logs of the app and all its tasks. If your task name is unique, grep this command's output for the task name to view task-specific logs.")) Expect(session).To(Say("EXAMPLES:")) @@ -29,6 +30,7 @@ var _ = Describe("run-task command", func() { Expect(session).To(Say("OPTIONS:")) Expect(session).To(Say(` --command, -c\s+The command to execute`)) Expect(session).To(Say(` -k Disk limit \(e\.g\. 256M, 1024M, 1G\)`)) + Expect(session).To(Say(` -l Log rate limit per second, in bytes \(e\.g\. 128B, 4K, 1M\). -l=-1 represents unlimited`)) Expect(session).To(Say(` -m Memory limit \(e\.g\. 256M, 1024M, 1G\)`)) Expect(session).To(Say(` --name Name to give the task \(generated if omitted\)`)) Expect(session).To(Say(` --process Process type to use as a template for command, memory, and disk for the created task`)) @@ -130,6 +132,32 @@ var _ = Describe("run-task command", func() { }) }) + When("log rate limit is provided", func() { + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + }) + When("the provided log rate limit is invalid", func() { + It("displays error and exits 1", func() { + session := helpers.CF("run-task", appName, "--command", "echo hi", "-l", "invalid") + Eventually(session).Should(Exit(1)) + Expect(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB")) + + }) + }) + + When("the provided log rate limit is valid", func() { + It("runs the task with the provided log rate limit", func() { + logRateLimit := 1024 + session := helpers.CF("run-task", appName, "--command", "echo hi", "-l", fmt.Sprintf("%dB", logRateLimit)) + Eventually(session).Should(Exit(0)) + + session = helpers.CF("tasks", appName, "-v") + Eventually(session).Should(Exit(0)) + Expect(session).To(Say("\"log_rate_limit_in_bytes_per_second\": %d", logRateLimit)) + }) + }) + }) + When("task memory is provided", func() { When("the provided memory is invalid", func() { It("displays error and exits 1", func() { diff --git a/integration/v7/isolated/scale_command_test.go b/integration/v7/isolated/scale_command_test.go index 1c3d3c3e3fa..a256094ca43 100644 --- a/integration/v7/isolated/scale_command_test.go +++ b/integration/v7/isolated/scale_command_test.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" "code.cloudfoundry.org/cli/integration/helpers" @@ -33,23 +34,24 @@ var _ = Describe("scale command", func() { It("appears in cf help -a", func() { session := helpers.CF("help", "-a") Eventually(session).Should(Exit(0)) - Expect(session).To(HaveCommandInCategoryWithDescription("scale", "APPS", "Change or view the instance count, disk space limit, and memory limit for an app")) + Expect(session).To(HaveCommandInCategoryWithDescription("scale", "APPS", "Change or view the instance count, disk space limit, memory limit, and log rate limit for an app")) }) It("displays command usage to output", func() { session := helpers.CF("scale", "--help") Eventually(session).Should(Say("NAME:")) - Eventually(session).Should(Say("scale - Change or view the instance count, disk space limit, and memory limit for an app")) + Eventually(session).Should(Say("scale - Change or view the instance count, disk space limit, memory limit, and log rate limit for an app")) Eventually(session).Should(Say("USAGE:")) - Eventually(session).Should(Say(`cf scale APP_NAME \[--process PROCESS\] \[-i INSTANCES\] \[-k DISK\] \[-m MEMORY\]`)) - Eventually(session).Should(Say("Modifying the app's disk or memory will cause the app to restart.")) + Eventually(session).Should(Say(`cf scale APP_NAME \[--process PROCESS\] \[-i INSTANCES\] \[-k DISK\] \[-m MEMORY\] \[-l LOG_RATE_LIMIT\]`)) + Eventually(session).Should(Say("Modifying the app's disk, memory, or log rate will cause the app to restart.")) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say(`-f\s+Force restart of app without prompt`)) Eventually(session).Should(Say(`-i\s+Number of instances`)) Eventually(session).Should(Say(`-k\s+Disk limit \(e\.g\. 256M, 1024M, 1G\)`)) + Eventually(session).Should(Say(`-l\s+Log rate limit per second, in bytes \(e\.g\. 128B, 4K, 1M\). -l=-1 represents unlimited`)) Eventually(session).Should(Say(`-m\s+Memory limit \(e\.g\. 256M, 1024M, 1G\)`)) Eventually(session).Should(Say(`--process\s+App process to scale \(Default: web\)`)) @@ -229,6 +231,37 @@ var _ = Describe("scale command", func() { }) }) + When("Scaling the log rate limit", func() { + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + }) + + It("scales log rate limit to 1M", func() { + buffer := NewBuffer() + _, err := buffer.Write([]byte("y\n")) + Expect(err).ToNot(HaveOccurred()) + session := helpers.CFWithStdin(buffer, "scale", appName, "-l", "1M") + Eventually(session).Should(Exit(0)) + Expect(session).To(Say(`Scaling app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) + Expect(session).To(Say(`This will cause the app to restart\. Are you sure you want to scale %s\? \[yN\]:`, appName)) + Expect(session).To(Say(`Stopping app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) + Expect(session).To(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) + Expect(session).To(Say(`Instances starting\.\.\.`)) + + helpers.WaitForLogRateLimitToTakeEffect(appName, 0, 0, false, "1M") + }) + + When("-f flag provided", func() { + It("scales without prompt", func() { + session := helpers.CF("scale", appName, "-l", "1M", "-f") + Eventually(session).Should(Exit(0)) + Expect(session).To(Say("Scaling app %s in org %s / space %s as %s...", appName, orgName, spaceName, userName)) + + helpers.WaitForLogRateLimitToTakeEffect(appName, 0, 0, false, "1M") + }) + }) + }) + When("Scaling to 0 instances", func() { It("scales to 0 instances", func() { session := helpers.CF("scale", appName, "-i", "0") diff --git a/integration/v7/isolated/space_quota_command_test.go b/integration/v7/isolated/space_quota_command_test.go index 34798df7ef7..d30a0fa7aee 100644 --- a/integration/v7/isolated/space_quota_command_test.go +++ b/integration/v7/isolated/space_quota_command_test.go @@ -85,6 +85,7 @@ var _ = Describe("space-quota command", func() { Eventually(session).Should(Say(`paid service plans:\s+allowed`)) Eventually(session).Should(Say(`app instances:\s+unlimited`)) Eventually(session).Should(Say(`route ports:\s+0`)) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) Eventually(session).Should(Exit(0)) }) diff --git a/integration/v7/isolated/space_quotas_command_test.go b/integration/v7/isolated/space_quotas_command_test.go index 880a561560b..c70a7392f60 100644 --- a/integration/v7/isolated/space_quotas_command_test.go +++ b/integration/v7/isolated/space_quotas_command_test.go @@ -62,8 +62,8 @@ var _ = Describe("space-quotas command", func() { It("lists the space quotas", func() { session := helpers.CF("space-quotas") Eventually(session).Should(Say(`Getting space quotas for org %s as %s\.\.\.`, orgName, userName)) - Eventually(session).Should(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports`)) - Eventually(session).Should(Say(`%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s`, quotaName, totalMemory, instanceMemory, routes, serviceInstances, "allowed", "unlimited", reservedRoutePorts)) + Eventually(session).Should(Say(`name\s+total memory\s+instance memory\s+routes\s+service instances\s+paid service plans\s+app instances\s+route ports\s+log volume per second`)) + Eventually(session).Should(Say(`%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s\s+%s`, quotaName, totalMemory, instanceMemory, routes, serviceInstances, "allowed", "unlimited", reservedRoutePorts, "unlimited")) Eventually(session).Should(Exit(0)) }) diff --git a/integration/v7/isolated/start_command_test.go b/integration/v7/isolated/start_command_test.go index 00d7887f79f..b1bbfac5a81 100644 --- a/integration/v7/isolated/start_command_test.go +++ b/integration/v7/isolated/start_command_test.go @@ -130,7 +130,7 @@ var _ = Describe("start command", func() { Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) Eventually(session).Should(Say(`memory usage:\s+32M`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+logging\s+details`)) Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) Eventually(session).Should(Exit(0)) @@ -187,7 +187,7 @@ var _ = Describe("start command", func() { Eventually(session).Should(Say(`requested state:\s+started`)) Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) - Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`)) + Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+logging\s+details`)) Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) Eventually(session).Should(Exit(0)) diff --git a/integration/v7/isolated/update_org_quota_command_test.go b/integration/v7/isolated/update_org_quota_command_test.go index e38efddd291..381c4004f44 100644 --- a/integration/v7/isolated/update_org_quota_command_test.go +++ b/integration/v7/isolated/update_org_quota_command_test.go @@ -1,6 +1,7 @@ package isolated import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/integration/helpers" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -16,7 +17,7 @@ var _ = Describe("update-org-quota command", func() { Eventually(session).Should(Say("NAME:")) Eventually(session).Should(Say("update-org-quota - Update an existing organization quota")) Eventually(session).Should(Say("USAGE:")) - Eventually(session).Should(Say(`cf update-org-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] \[-n NEW_NAME\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans | --disallow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\]`)) + Eventually(session).Should(Say(`cf update-org-quota QUOTA [-m TOTAL_MEMORY] [-i INSTANCE_MEMORY] \[-n NEW_NAME\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans | --disallow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\] \[-l LOG_VOLUME\]`)) Eventually(session).Should(Say("ALIAS:")) Eventually(session).Should(Say("update-quota")) Eventually(session).Should(Say("OPTIONS:")) @@ -29,6 +30,7 @@ var _ = Describe("update-org-quota command", func() { Eventually(session).Should(Say(`-r\s+Total number of routes. -1 represents an unlimited amount.`)) Eventually(session).Should(Say(`--reserved-route-ports\s+Maximum number of routes that may be created with ports. -1 represents an unlimited amount.`)) Eventually(session).Should(Say(`-s\s+Total number of service instances. -1 represents an unlimited amount.`)) + Eventually(session).Should(Say(`-l\s+Total log volume per second all processes can have, in bytes \(e.g. 128B, 4K, 1M\). -1 represents an unlimited amount.`)) Eventually(session).Should(Say("SEE ALSO:")) Eventually(session).Should(Say("org, org-quota")) Eventually(session).Should(Exit(0)) @@ -83,9 +85,27 @@ var _ = Describe("update-org-quota command", func() { Eventually(session).Should(Say(`paid service plans:\s+%s`, "disallowed")) Eventually(session).Should(Say(`app instances:\s+%s`, appInstances)) Eventually(session).Should(Say(`route ports:\s+%s`, reservedRoutePorts)) + Eventually(session).Should(Say(`log volume per second:\s+unlimited`)) Eventually(session).Should(Exit(0)) }) + When("CAPI supports log rate limits", func() { + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + }) + + It("updates a quota", func() { + logVolume := "500B" + session := helpers.CF("update-org-quota", quotaName, "-l", logVolume) + Eventually(session).Should(Say(`Updating org quota %s as %s\.\.\.`, quotaName, username)) + Eventually(session).Should(Exit(0)) + + session = helpers.CF("org-quota", quotaName) + Eventually(session).Should(Say(`log volume per second:\s+%s`, logVolume)) + Eventually(session).Should(Exit(0)) + }) + }) + When("the -n rename flag is provided", func() { var newQuotaName string diff --git a/integration/v7/isolated/update_space_quota_command_test.go b/integration/v7/isolated/update_space_quota_command_test.go index 5d70363ba18..4b2637d9610 100644 --- a/integration/v7/isolated/update_space_quota_command_test.go +++ b/integration/v7/isolated/update_space_quota_command_test.go @@ -1,6 +1,7 @@ package isolated import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/integration/helpers" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -24,7 +25,7 @@ var _ = Describe("update-space-quota command", func() { Eventually(session).Should(Say("NAME:")) Eventually(session).Should(Say("update-space-quota - Update an existing space quota")) Eventually(session).Should(Say("USAGE:")) - Eventually(session).Should(Say(`cf update-space-quota QUOTA \[-m TOTAL_MEMORY\] \[-i INSTANCE_MEMORY\] \[-n NEW_NAME\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans | --disallow-paid-service-plans\] \[--allow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\]`)) + Eventually(session).Should(Say(`cf update-space-quota QUOTA \[-m TOTAL_MEMORY\] \[-i INSTANCE_MEMORY\] \[-n NEW_NAME\] \[-r ROUTES\] \[-s SERVICE_INSTANCES\] \[-a APP_INSTANCES\] \[--allow-paid-service-plans | --disallow-paid-service-plans\] \[--allow-paid-service-plans\] \[--reserved-route-ports RESERVED_ROUTE_PORTS\] \[-l LOG_VOLUME\]`)) Eventually(session).Should(Say("OPTIONS:")) Eventually(session).Should(Say(`-a\s+Total number of application instances. -1 represents an unlimited amount.`)) Eventually(session).Should(Say(`--allow-paid-service-plans\s+Allow provisioning instances of paid service plans.`)) @@ -35,6 +36,7 @@ var _ = Describe("update-space-quota command", func() { Eventually(session).Should(Say(`-r\s+Total number of routes. -1 represents an unlimited amount.`)) Eventually(session).Should(Say(`--reserved-route-ports\s+Maximum number of routes that may be created with ports. -1 represents an unlimited amount.`)) Eventually(session).Should(Say(`-s\s+Total number of service instances. -1 represents an unlimited amount.`)) + Eventually(session).Should(Say(`-l\s+Total log volume per second all processes can have, in bytes \(e.g. 128B, 4K, 1M\). -1 represents an unlimited amount.`)) Eventually(session).Should(Say("SEE ALSO:")) Eventually(session).Should(Say("space, space-quota, space-quotas")) Eventually(session).Should(Exit(0)) @@ -94,9 +96,28 @@ var _ = Describe("update-space-quota command", func() { Eventually(session).Should(Say(`paid service plans:\s+%s`, "allowed")) Eventually(session).Should(Say(`app instances:\s+%s`, appInstances)) Eventually(session).Should(Say(`route ports:\s+%s`, reservedRoutePorts)) + Eventually(session).Should(Say(`log volume per second:\s+%s`, "unlimited")) Eventually(session).Should(Exit(0)) }) + When("CAPI supports log rate limits", func() { + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + }) + + It("updates a quota", func() { + logVolume := "500B" + session := helpers.CF("update-space-quota", quotaName, "-l", logVolume) + Eventually(session).Should(Say("Updating space quota %s for org %s as %s...", quotaName, orgName, userName)) + Eventually(session).Should(Say("OK")) + Eventually(session).Should(Exit(0)) + + session = helpers.CF("space-quota", quotaName) + Eventually(session).Should(Say(`log volume per second:\s+%s`, logVolume)) + Eventually(session).Should(Exit(0)) + }) + }) + When("the named quota does not exist", func() { It("displays a missing quota error message and fails", func() { session := helpers.CF("update-space-quota", "bogota") diff --git a/integration/v7/push/combination_manifest_and_flag_test.go b/integration/v7/push/combination_manifest_and_flag_test.go index 315767dd266..f870b9fff7b 100644 --- a/integration/v7/push/combination_manifest_and_flag_test.go +++ b/integration/v7/push/combination_manifest_and_flag_test.go @@ -209,6 +209,7 @@ var _ = Describe("push with a simple manifest and flags", func() { Entry("health check type", "-u", "port"), Entry("instances", "-i", "10"), Entry("memory", "-m", "100M"), + Entry("log rate limit", "-l", "5K"), Entry("no route", "--no-route"), Entry("stack", "-s", "something"), ) diff --git a/integration/v7/push/help_test.go b/integration/v7/push/help_test.go index 526c611d0fc..1529492af15 100644 --- a/integration/v7/push/help_test.go +++ b/integration/v7/push/help_test.go @@ -31,6 +31,7 @@ var _ = Describe("help", func() { "[-i NUM_INSTANCES]", "[-k DISK]", "[-m MEMORY]", + "[-l LOG_RATE_LIMIT]", "[-p PATH]", "[-s STACK]", "[-t HEALTH_TIMEOUT]", @@ -55,6 +56,7 @@ var _ = Describe("help", func() { "[-i NUM_INSTANCES]", "[-k DISK]", "[-m MEMORY]", + "[-l LOG_RATE_LIMIT]", "[-p PATH]", "[-s STACK]", "[-t HEALTH_TIMEOUT]", @@ -68,15 +70,16 @@ var _ = Describe("help", func() { assertUsage(session, buildpackAppUsage, dockerAppUsage) Eventually(session).Should(Say("OPTIONS:")) - Eventually(session).Should(Say(`app-start-timeout, -t`)) + Eventually(session).Should(Say(`--app-start-timeout, -t`)) Eventually(session).Should(Say(`--buildpack, -b`)) - Eventually(session).Should(Say(`disk, -k`)) + Eventually(session).Should(Say(`--disk, -k`)) Eventually(session).Should(Say(`--docker-image, -o`)) Eventually(session).Should(Say(`--docker-username`)) Eventually(session).Should(Say(`--droplet`)) Eventually(session).Should(Say(`--endpoint`)) Eventually(session).Should(Say(`--health-check-type, -u`)) Eventually(session).Should(Say(`--instances, -i`)) + Eventually(session).Should(Say(`--log-rate-limit, -l\s+Log rate limit per second, in bytes \(e.g. 128B, 4K, 1M\). -l=-1 represents unlimited`)) Eventually(session).Should(Say(`--manifest, -f`)) Eventually(session).Should(Say(`--memory, -m`)) Eventually(session).Should(Say(`--no-manifest`)) diff --git a/integration/v7/push/log_rate_limit_flag_test.go b/integration/v7/push/log_rate_limit_flag_test.go new file mode 100644 index 00000000000..a8797b43db7 --- /dev/null +++ b/integration/v7/push/log_rate_limit_flag_test.go @@ -0,0 +1,35 @@ +package push + +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("push with log rate limit flag", func() { + var ( + appName string + ) + + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionLogRateLimitingV3) + + appName = helpers.NewAppName() + }) + + Context("when the -l flag is provided with application log rate limit", func() { + It("creates the app with the specified log rate limit", func() { + helpers.WithHelloWorldApp(func(dir string) { + session := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, + PushCommandName, appName, + "-l", "5K", + ) + Eventually(session).Should(Exit(0)) + }) + + helpers.WaitForLogRateLimitToTakeEffect(appName, 0, 0, false, "5K") + }) + }) +}) diff --git a/resources/process_resource.go b/resources/process_resource.go index 5376f8e6ab6..afbf20a09a0 100644 --- a/resources/process_resource.go +++ b/resources/process_resource.go @@ -21,6 +21,7 @@ type Process struct { Instances types.NullInt MemoryInMB types.NullUint64 DiskInMB types.NullUint64 + LogRateLimitInBPS types.NullInt AppGUID string } @@ -31,6 +32,7 @@ func (p Process) MarshalJSON() ([]byte, error) { marshalInstances(p, &ccProcess) marshalMemory(p, &ccProcess) marshalDisk(p, &ccProcess) + marshalLogRateLimit(p, &ccProcess) marshalHealthCheck(p, &ccProcess) return json.Marshal(ccProcess) @@ -38,13 +40,14 @@ func (p Process) MarshalJSON() ([]byte, error) { func (p *Process) UnmarshalJSON(data []byte) error { var ccProcess struct { - Command types.FilteredString `json:"command"` - DiskInMB types.NullUint64 `json:"disk_in_mb"` - GUID string `json:"guid"` - Instances types.NullInt `json:"instances"` - MemoryInMB types.NullUint64 `json:"memory_in_mb"` - Type string `json:"type"` - Relationships Relationships `json:"relationships"` + Command types.FilteredString `json:"command"` + DiskInMB types.NullUint64 `json:"disk_in_mb"` + GUID string `json:"guid"` + Instances types.NullInt `json:"instances"` + MemoryInMB types.NullUint64 `json:"memory_in_mb"` + LogRateLimitInBPS types.NullInt `json:"log_rate_limit_in_bytes_per_second"` + Type string `json:"type"` + Relationships Relationships `json:"relationships"` HealthCheck struct { Type constant.HealthCheckType `json:"type"` @@ -70,6 +73,7 @@ func (p *Process) UnmarshalJSON(data []byte) error { p.HealthCheckType = ccProcess.HealthCheck.Type p.Instances = ccProcess.Instances p.MemoryInMB = ccProcess.MemoryInMB + p.LogRateLimitInBPS = ccProcess.LogRateLimitInBPS p.Type = ccProcess.Type p.AppGUID = ccProcess.Relationships[constant.RelationshipTypeApplication].GUID @@ -86,10 +90,11 @@ type healthCheck struct { } type marshalProcess struct { - Command interface{} `json:"command,omitempty"` - Instances json.Number `json:"instances,omitempty"` - MemoryInMB json.Number `json:"memory_in_mb,omitempty"` - DiskInMB json.Number `json:"disk_in_mb,omitempty"` + Command interface{} `json:"command,omitempty"` + Instances json.Number `json:"instances,omitempty"` + MemoryInMB json.Number `json:"memory_in_mb,omitempty"` + DiskInMB json.Number `json:"disk_in_mb,omitempty"` + LogRateLimitInBPS json.Number `json:"log_rate_limit_in_bytes_per_second,omitempty"` HealthCheck *healthCheck `json:"health_check,omitempty"` } @@ -129,3 +134,9 @@ func marshalMemory(p Process, ccProcess *marshalProcess) { ccProcess.MemoryInMB = json.Number(fmt.Sprint(p.MemoryInMB.Value)) } } + +func marshalLogRateLimit(p Process, ccProcess *marshalProcess) { + if p.LogRateLimitInBPS.IsSet { + ccProcess.LogRateLimitInBPS = json.Number(fmt.Sprint(p.LogRateLimitInBPS.Value)) + } +} diff --git a/resources/process_resource_test.go b/resources/process_resource_test.go index 394e8f1a61e..946c6ab9c70 100644 --- a/resources/process_resource_test.go +++ b/resources/process_resource_test.go @@ -64,6 +64,17 @@ var _ = Describe("Process", func() { }) }) + When("log rate limit is provided", func() { + BeforeEach(func() { + process = resources.Process{ + LogRateLimitInBPS: types.NullInt{Value: 1024, IsSet: true}, + } + }) + + It("sets the log rate limit to be set", func() { + Expect(string(processBytes)).To(MatchJSON(`{"log_rate_limit_in_bytes_per_second": 1024}`)) + }) + }) When("health check type http is provided", func() { BeforeEach(func() { process = resources.Process{ @@ -126,6 +137,17 @@ var _ = Describe("Process", func() { err = json.Unmarshal(processBytes, &process) Expect(err).ToNot(HaveOccurred()) }) + When("log rate limit is provided", func() { + BeforeEach(func() { + processBytes = []byte(`{"log_rate_limit_in_bytes_per_second": 512}`) + }) + + It("sets the log rate limit", func() { + Expect(process).To(MatchFields(IgnoreExtras, Fields{ + "LogRateLimitInBPS": Equal(types.NullInt{Value: 512, IsSet: true}), + })) + }) + }) When("health check type http is provided", func() { BeforeEach(func() { processBytes = []byte(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`) diff --git a/resources/quota_resource.go b/resources/quota_resource.go index f78ec80699b..8a896c14582 100644 --- a/resources/quota_resource.go +++ b/resources/quota_resource.go @@ -23,6 +23,7 @@ type AppLimit struct { TotalMemory *types.NullInt `json:"total_memory_in_mb,omitempty"` InstanceMemory *types.NullInt `json:"per_process_memory_in_mb,omitempty"` TotalAppInstances *types.NullInt `json:"total_instances,omitempty"` + TotalLogVolume *types.NullInt `json:"log_rate_limit_in_bytes_per_second,omitempty"` } func (al *AppLimit) UnmarshalJSON(rawJSON []byte) error { @@ -57,6 +58,13 @@ func (al *AppLimit) UnmarshalJSON(rawJSON []byte) error { } } + if al.TotalLogVolume == nil { + al.TotalLogVolume = &types.NullInt{ + IsSet: false, + Value: 0, + } + } + return nil } diff --git a/resources/quota_resource_test.go b/resources/quota_resource_test.go index fb5e96ed80b..14273d96ee1 100644 --- a/resources/quota_resource_test.go +++ b/resources/quota_resource_test.go @@ -20,6 +20,9 @@ var _ = Describe("quota limits", func() { Entry("total memory", AppLimit{TotalMemory: &types.NullInt{IsSet: true, Value: 1}}, []byte(`{"total_memory_in_mb":1}`)), Entry("total memory", AppLimit{TotalMemory: nil}, []byte(`{}`)), Entry("total memory", AppLimit{TotalMemory: &types.NullInt{IsSet: false}}, []byte(`{"total_memory_in_mb":null}`)), + Entry("total log volume", AppLimit{TotalLogVolume: &types.NullInt{IsSet: true, Value: 1}}, []byte(`{"log_rate_limit_in_bytes_per_second":1}`)), + Entry("total log volume", AppLimit{TotalLogVolume: nil}, []byte(`{}`)), + Entry("total log volume", AppLimit{TotalLogVolume: &types.NullInt{IsSet: false}}, []byte(`{"log_rate_limit_in_bytes_per_second":null}`)), Entry("instance memory", AppLimit{InstanceMemory: &types.NullInt{IsSet: true, Value: 1}}, []byte(`{"per_process_memory_in_mb":1}`)), Entry("instance memory", AppLimit{InstanceMemory: nil}, []byte(`{}`)), Entry("instance memory", AppLimit{InstanceMemory: &types.NullInt{IsSet: false}}, []byte(`{"per_process_memory_in_mb":null}`)), @@ -37,35 +40,48 @@ var _ = Describe("quota limits", func() { }, Entry( "no null values", - []byte(`{"total_memory_in_mb":1,"per_process_memory_in_mb":2,"total_instances":3}`), + []byte(`{"total_memory_in_mb":1,"per_process_memory_in_mb":2,"total_instances":3,"log_rate_limit_in_bytes_per_second":4}`), AppLimit{ TotalMemory: &types.NullInt{IsSet: true, Value: 1}, InstanceMemory: &types.NullInt{IsSet: true, Value: 2}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 3}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 4}, }), Entry( "total memory is null", - []byte(`{"total_memory_in_mb":null,"per_process_memory_in_mb":2,"total_instances":3}`), + []byte(`{"total_memory_in_mb":null,"per_process_memory_in_mb":2,"total_instances":3,"log_rate_limit_in_bytes_per_second":4}`), AppLimit{ TotalMemory: &types.NullInt{IsSet: false, Value: 0}, InstanceMemory: &types.NullInt{IsSet: true, Value: 2}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 3}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 4}, }), Entry( "per process memory is null", - []byte(`{"total_memory_in_mb":1,"per_process_memory_in_mb":null,"total_instances":3}`), + []byte(`{"total_memory_in_mb":1,"per_process_memory_in_mb":null,"total_instances":3,"log_rate_limit_in_bytes_per_second":4}`), AppLimit{ TotalMemory: &types.NullInt{IsSet: true, Value: 1}, InstanceMemory: &types.NullInt{IsSet: false, Value: 0}, TotalAppInstances: &types.NullInt{IsSet: true, Value: 3}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 4}, }), Entry( "total instances is null", - []byte(`{"total_memory_in_mb":1,"per_process_memory_in_mb":2,"total_instances":null}`), + []byte(`{"total_memory_in_mb":1,"per_process_memory_in_mb":2,"total_instances":null,"log_rate_limit_in_bytes_per_second":4}`), AppLimit{ TotalMemory: &types.NullInt{IsSet: true, Value: 1}, InstanceMemory: &types.NullInt{IsSet: true, Value: 2}, TotalAppInstances: &types.NullInt{IsSet: false, Value: 0}, + TotalLogVolume: &types.NullInt{IsSet: true, Value: 4}, + }), + Entry( + "total log volume is null", + []byte(`{"total_memory_in_mb":1,"per_process_memory_in_mb":2,"total_instances":3,"log_rate_limit_in_bytes_per_second":null}`), + AppLimit{ + TotalMemory: &types.NullInt{IsSet: true, Value: 1}, + InstanceMemory: &types.NullInt{IsSet: true, Value: 2}, + TotalAppInstances: &types.NullInt{IsSet: true, Value: 3}, + TotalLogVolume: &types.NullInt{IsSet: false, Value: 0}, }), ) }) diff --git a/resources/space_quota_resource.go b/resources/space_quota_resource.go index 0fc2a110227..f1e6742eef5 100644 --- a/resources/space_quota_resource.go +++ b/resources/space_quota_resource.go @@ -23,6 +23,9 @@ func (sq SpaceQuota) MarshalJSON() ([]byte, error) { if sq.Apps.TotalAppInstances != nil { appLimits["total_instances"] = sq.Apps.TotalAppInstances } + if sq.Apps.TotalLogVolume != nil { + appLimits["log_rate_limit_in_bytes_per_second"] = sq.Apps.TotalLogVolume + } serviceLimits := map[string]interface{}{} if sq.Services.PaidServicePlans != nil { diff --git a/resources/task_resource.go b/resources/task_resource.go index 4bc1f96f012..62f1f500ed4 100644 --- a/resources/task_resource.go +++ b/resources/task_resource.go @@ -15,6 +15,8 @@ type Task struct { DiskInMB uint64 `json:"disk_in_mb,omitempty"` // GUID represents the unique task identifier. GUID string `json:"guid,omitempty"` + // LogRateLimitInBPS represents the log rate limit in bytes allocated for the task. + LogRateLimitInBPS int `json:"log_rate_limit_in_bytes_per_second,omitempty"` // MemoryInMB represents the memory in MB allocated for the task. MemoryInMB uint64 `json:"memory_in_mb,omitempty"` // Name represents the name of the task. diff --git a/util/manifestparser/application.go b/util/manifestparser/application.go index c51121f8e61..4eae68b1ecc 100644 --- a/util/manifestparser/application.go +++ b/util/manifestparser/application.go @@ -30,6 +30,7 @@ type Application struct { RandomRoute bool `yaml:"random-route,omitempty"` DefaultRoute bool `yaml:"default-route,omitempty"` Stack string `yaml:"stack,omitempty"` + LogRateLimit string `yaml:"log-rate-limit-per-second,omitempty"` RemainingManifestFields map[string]interface{} `yaml:"-,inline"` } diff --git a/util/manifestparser/application_test.go b/util/manifestparser/application_test.go index 467274711a9..d39eb28db8a 100644 --- a/util/manifestparser/application_test.go +++ b/util/manifestparser/application_test.go @@ -349,6 +349,22 @@ processes: }) }) + Context("when a log rate limit is provided", func() { + BeforeEach(func() { + rawYAML = []byte(`--- +processes: +- log-rate-limit-per-second: 512M +`) + }) + + It("unmarshals the processes property with the log rate limit", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(application.Processes).To(Equal([]Process{ + {LogRateLimit: "512M", RemainingManifestFields: emptyMap}, + })) + }) + }) + Context("when an unknown field is provided", func() { BeforeEach(func() { rawYAML = []byte(`--- @@ -392,6 +408,19 @@ processes: Expect(remarshalledYaml).To(MatchYAML(rawYAML)) }) }) + + Context("when a log rate limit is provided", func() { + BeforeEach(func() { + rawYAML = []byte(`--- +log-rate-limit-per-second: 5K +`) + }) + + It("unmarshals the log rate limit", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(application.LogRateLimit).To(Equal("5K")) + }) + }) }) Describe("SetStartCommand", func() { diff --git a/util/manifestparser/process.go b/util/manifestparser/process.go index 611cbf2a1df..6481225a6af 100644 --- a/util/manifestparser/process.go +++ b/util/manifestparser/process.go @@ -16,6 +16,7 @@ type Process struct { Instances *int `yaml:"instances,omitempty"` Memory string `yaml:"memory,omitempty"` Type string `yaml:"type"` + LogRateLimit string `yaml:"log-rate-limit-per-second,omitempty"` RemainingManifestFields map[string]interface{} `yaml:"-,inline"` } diff --git a/util/manifestparser/process_test.go b/util/manifestparser/process_test.go index 33753ba85bf..564c2dbea3e 100644 --- a/util/manifestparser/process_test.go +++ b/util/manifestparser/process_test.go @@ -77,6 +77,5 @@ var _ = Describe("Process", func() { Expect(process).To(Equal(Process{DiskQuota: "5G", RemainingManifestFields: map[string]interface{}{}})) }) }) - }) }) From 41ebecb29f603c571cd0d5b3a4aa448e91007e10 Mon Sep 17 00:00:00 2001 From: George Blue Date: Wed, 14 Sep 2022 18:30:16 +0200 Subject: [PATCH 027/543] feat: add --wait flag to upgrade-service (#2285) Most of the service commands (e.g. "cf update-service") take a --wait flag to wait for completion. For some reason "cf upgrade-service" did not, and this commit resolves that inconsistency. --- actor/v7action/service_instance.go | 16 ++- actor/v7action/service_instance_test.go | 45 ++++--- command/v7/actor.go | 2 +- command/v7/upgrade_service_command.go | 25 ++-- command/v7/upgrade_service_command_test.go | 118 +++++++++++++++--- command/v7/v7fakes/fake_actor.go | 43 ++++--- .../isolated/upgrade_service_command_test.go | 4 +- 7 files changed, 184 insertions(+), 69 deletions(-) diff --git a/actor/v7action/service_instance.go b/actor/v7action/service_instance.go index 7ac5c7bbd5c..b1fa9b68e44 100644 --- a/actor/v7action/service_instance.go +++ b/actor/v7action/service_instance.go @@ -146,10 +146,13 @@ func (actor Actor) UpdateManagedServiceInstance(params UpdateManagedServiceInsta return stream, Warnings(warnings), err } -func (actor Actor) UpgradeManagedServiceInstance(serviceInstanceName string, spaceGUID string) (Warnings, error) { - var serviceInstance resources.ServiceInstance - var servicePlan resources.ServicePlan - var jobURL ccv3.JobURL +func (actor Actor) UpgradeManagedServiceInstance(serviceInstanceName string, spaceGUID string) (chan PollJobEvent, Warnings, error) { + var ( + serviceInstance resources.ServiceInstance + servicePlan resources.ServicePlan + jobURL ccv3.JobURL + stream chan PollJobEvent + ) warnings, err := railway.Sequentially( func() (warnings ccv3.Warnings, err error) { @@ -173,11 +176,12 @@ func (actor Actor) UpgradeManagedServiceInstance(serviceInstanceName string, spa return }, func() (warnings ccv3.Warnings, err error) { - return actor.CloudControllerClient.PollJobForState(jobURL, constant.JobPolling) + stream = actor.PollJobToEventStream(jobURL) + return }, ) - return Warnings(warnings), err + return stream, Warnings(warnings), err } func (actor Actor) RenameServiceInstance(currentServiceInstanceName, spaceGUID, newServiceInstanceName string) (Warnings, error) { diff --git a/actor/v7action/service_instance_test.go b/actor/v7action/service_instance_test.go index cb1551a3db1..03a7ca94f6e 100644 --- a/actor/v7action/service_instance_test.go +++ b/actor/v7action/service_instance_test.go @@ -493,7 +493,7 @@ var _ = Describe("Service Instance Actions", func() { }) It("returns warnings and an error", func() { - //Expect(warnings).To(ContainElement("warning from get")) + Expect(warnings).To(ContainElement("warning from get")) Expect(executeErr).To(MatchError("bang")) }) }) @@ -1282,12 +1282,13 @@ var _ = Describe("Service Instance Actions", func() { ) var ( - err error - warnings Warnings + executeErr error + warnings Warnings + stream chan PollJobEvent ) JustBeforeEach(func() { - warnings, err = actor.UpgradeManagedServiceInstance(fakeServiceInstanceName, fakeSpaceGUID) + stream, warnings, executeErr = actor.UpgradeManagedServiceInstance(fakeServiceInstanceName, fakeSpaceGUID) }) It("makes a request to get the service instance", func() { @@ -1313,8 +1314,9 @@ var _ = Describe("Service Instance Actions", func() { }) It("returns the appropriate error", func() { + Expect(stream).To(BeNil()) Expect(warnings).To(ConsistOf("get SI warning")) - Expect(err).To(MatchError(actionerror.ServiceInstanceNotFoundError{ + Expect(executeErr).To(MatchError(actionerror.ServiceInstanceNotFoundError{ Name: fakeServiceInstanceName, })) }) @@ -1342,8 +1344,9 @@ var _ = Describe("Service Instance Actions", func() { }) It("returns the appropriate error", func() { + Expect(stream).To(BeNil()) Expect(warnings).To(ConsistOf("get SI warning")) - Expect(err).To(MatchError(actionerror.ServiceInstanceUpgradeNotAvailableError{})) + Expect(executeErr).To(MatchError(actionerror.ServiceInstanceUpgradeNotAvailableError{})) }) }) @@ -1380,10 +1383,15 @@ var _ = Describe("Service Instance Actions", func() { ccv3.Warnings{"warning from update"}, nil, ) - fakeCloudControllerClient.PollJobForStateReturns( - ccv3.Warnings{"warning from poll"}, - nil, - ) + + fakeStream := make(chan ccv3.PollJobEvent) + go func() { + fakeStream <- ccv3.PollJobEvent{ + State: constant.JobProcessing, + Warnings: ccv3.Warnings{"stream warning"}, + } + }() + fakeCloudControllerClient.PollJobToEventStreamReturns(fakeStream) }) It("makes the right calls and returns all warnings", func() { @@ -1403,15 +1411,18 @@ var _ = Describe("Service Instance Actions", func() { }) By("polling the job", func() { - Expect(fakeCloudControllerClient.PollJobForStateCallCount()).To(Equal(1)) - actualURL, actualState := fakeCloudControllerClient.PollJobForStateArgsForCall(0) - Expect(actualURL).To(Equal(jobURL)) - Expect(actualState).To(Equal(constant.JobPolling)) + Expect(fakeCloudControllerClient.PollJobToEventStreamCallCount()).To(Equal(1)) + Expect(fakeCloudControllerClient.PollJobToEventStreamArgsForCall(0)).To(Equal(jobURL)) }) - By("returning warnings and no error", func() { - Expect(err).NotTo(HaveOccurred()) - Expect(warnings).To(ConsistOf("warning from get", "warning from plan", "warning from update", "warning from poll")) + By("returning a stream, warnings and no error", func() { + Eventually(stream).Should(Receive(Equal(PollJobEvent{ + State: JobProcessing, + Warnings: Warnings{"stream warning"}, + }))) + + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("warning from get", "warning from plan", "warning from update")) }) }) }) diff --git a/command/v7/actor.go b/command/v7/actor.go index 3bbae507c69..b6173934d63 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -237,7 +237,7 @@ type Actor interface { UpdateDestination(string, string, string) (v7action.Warnings, error) UpdateDomainLabelsByDomainName(string, map[string]types.NullString) (v7action.Warnings, error) UpdateManagedServiceInstance(params v7action.UpdateManagedServiceInstanceParams) (chan v7action.PollJobEvent, v7action.Warnings, error) - UpgradeManagedServiceInstance(serviceInstanceName, spaceGUID string) (v7action.Warnings, error) + UpgradeManagedServiceInstance(serviceInstanceName, spaceGUID string) (chan v7action.PollJobEvent, v7action.Warnings, error) UpdateOrganizationLabelsByOrganizationName(string, map[string]types.NullString) (v7action.Warnings, error) UpdateOrganizationQuota(quotaName string, newName string, limits v7action.QuotaLimits) (v7action.Warnings, error) UpdateProcessByTypeAndApplication(processType string, appGUID string, updatedProcess resources.Process) (v7action.Warnings, error) diff --git a/command/v7/upgrade_service_command.go b/command/v7/upgrade_service_command.go index 81bd39a065a..91952e09f64 100644 --- a/command/v7/upgrade_service_command.go +++ b/command/v7/upgrade_service_command.go @@ -4,6 +4,7 @@ import ( "code.cloudfoundry.org/cli/actor/actionerror" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" + "code.cloudfoundry.org/cli/command/v7/shared" ) type UpgradeServiceCommand struct { @@ -11,6 +12,7 @@ type UpgradeServiceCommand struct { RequiredArgs flag.ServiceInstance `positional-args:"yes"` Force bool `short:"f" long:"force" description:"Force upgrade without asking for confirmation"` + Wait bool `short:"w" long:"wait" description:"Wait for the operation to complete"` relatedCommands interface{} `related_commands:"services, update-service, update-user-provided-service"` } @@ -38,7 +40,7 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { serviceInstanceName := string(cmd.RequiredArgs.ServiceInstance) - warnings, actorError := cmd.Actor.UpgradeManagedServiceInstance( + stream, warnings, actorError := cmd.Actor.UpgradeManagedServiceInstance( serviceInstanceName, cmd.Config.TargetedSpace().GUID, ) @@ -46,8 +48,6 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { switch actorError.(type) { case nil: - cmd.displayUpgradeInProgressMessage() - cmd.UI.DisplayOK() case actionerror.ServiceInstanceUpgradeNotAvailableError: cmd.UI.DisplayText(actorError.Error()) cmd.UI.DisplayOK() @@ -57,6 +57,17 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { return actorError } + complete, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + switch { + case err != nil: + return err + case complete: + cmd.UI.DisplayTextWithFlavor("Upgrade of service instance {{.ServiceInstanceName}} complete.", cmd.serviceInstanceName()) + default: + cmd.UI.DisplayTextWithFlavor("Upgrade in progress. Use 'cf services' or 'cf service {{.ServiceInstanceName}}' to check operation status.", cmd.serviceInstanceName()) + } + + cmd.UI.DisplayOK() return nil } @@ -79,6 +90,7 @@ func (cmd UpgradeServiceCommand) displayEvent() error { "Username": user.Name, }, ) + cmd.UI.DisplayNewline() return nil } @@ -101,13 +113,6 @@ func (cmd UpgradeServiceCommand) displayPrompt() (bool, error) { return upgrade, nil } -func (cmd UpgradeServiceCommand) displayUpgradeInProgressMessage() { - cmd.UI.DisplayTextWithFlavor("Upgrade in progress. Use 'cf services' or 'cf service {{.ServiceInstance}}' to check operation status.", - map[string]interface{}{ - "ServiceInstance": cmd.RequiredArgs.ServiceInstance, - }) -} - func (cmd UpgradeServiceCommand) serviceInstanceName() map[string]interface{} { return map[string]interface{}{ "ServiceInstanceName": cmd.RequiredArgs.ServiceInstance, diff --git a/command/v7/upgrade_service_command_test.go b/command/v7/upgrade_service_command_test.go index bc8aa3f54fa..8bbd7d8dcfb 100644 --- a/command/v7/upgrade_service_command_test.go +++ b/command/v7/upgrade_service_command_test.go @@ -76,6 +76,7 @@ var _ = Describe("upgrade-service command", func() { When("the service instance does not exist", func() { BeforeEach(func() { fakeActor.UpgradeManagedServiceInstanceReturns( + nil, v7action.Warnings{"upgrade warning"}, actionerror.ServiceInstanceNotFoundError{Name: serviceInstanceName}, ) @@ -89,37 +90,126 @@ var _ = Describe("upgrade-service command", func() { }) }) - When("the service instance upgrade starts successfully", func() { + When("the actor returns an unexpected error", func() { BeforeEach(func() { fakeActor.UpgradeManagedServiceInstanceReturns( + make(chan v7action.PollJobEvent), v7action.Warnings{"upgrade warning"}, - nil, + errors.New("bang"), ) }) - It("succeeds with a message", func() { - Expect(executeErr).NotTo(HaveOccurred()) + It("fails with warnings", func() { + Expect(executeErr).To(MatchError("bang")) Expect(testUI.Err).To(Say("upgrade warning")) + Expect(testUI.Out).NotTo(Say("OK")) + }) + }) + + When("stream goes to polling", func() { + BeforeEach(func() { + fakeStream := make(chan v7action.PollJobEvent) + fakeActor.UpgradeManagedServiceInstanceReturns( + fakeStream, + v7action.Warnings{"actor warning"}, + nil, + ) + + go func() { + fakeStream <- v7action.PollJobEvent{ + State: v7action.JobPolling, + Warnings: v7action.Warnings{"poll warning"}, + } + }() + }) + + It("prints messages and warnings", func() { Expect(testUI.Out).To(SatisfyAll( - Say("\n"), - Say(`Upgrade in progress. Use 'cf services' or 'cf service %s' to check operation status\.\n`, serviceInstanceName), - Say("OK\n"), + Say(`Upgrading service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username), + Say(`\n`), + Say(`Upgrade in progress. Use 'cf services' or 'cf service %s' to check operation status\.`, serviceInstanceName), + Say(`OK\n`), + )) + + Expect(testUI.Err).To(SatisfyAll( + Say("actor warning"), + Say("poll warning"), )) }) }) - When("the actor returns an unexpected error", func() { + When("error in event stream", func() { BeforeEach(func() { + setFlag(&cmd, "--wait") + + fakeStream := make(chan v7action.PollJobEvent) fakeActor.UpgradeManagedServiceInstanceReturns( - v7action.Warnings{"upgrade warning"}, - errors.New("bang"), + fakeStream, + v7action.Warnings{"a warning"}, + nil, ) + + go func() { + fakeStream <- v7action.PollJobEvent{ + State: v7action.JobPolling, + Warnings: v7action.Warnings{"poll warning"}, + } + fakeStream <- v7action.PollJobEvent{ + State: v7action.JobFailed, + Warnings: v7action.Warnings{"failed warning"}, + Err: errors.New("boom"), + } + }() }) - It("fails with warnings", func() { - Expect(executeErr).To(MatchError("bang")) - Expect(testUI.Err).To(Say("upgrade warning")) - Expect(testUI.Out).NotTo(Say("OK")) + It("returns the error and prints warnings", func() { + Expect(executeErr).To(MatchError("boom")) + Expect(testUI.Err).To(SatisfyAll( + Say("poll warning"), + Say("failed warning"), + )) + }) + }) + + When("--wait flag specified", func() { + BeforeEach(func() { + setFlag(&cmd, "--wait") + + fakeStream := make(chan v7action.PollJobEvent) + fakeActor.UpgradeManagedServiceInstanceReturns( + fakeStream, + v7action.Warnings{"a warning"}, + nil, + ) + + go func() { + fakeStream <- v7action.PollJobEvent{ + State: v7action.JobPolling, + Warnings: v7action.Warnings{"poll warning"}, + } + fakeStream <- v7action.PollJobEvent{ + State: v7action.JobComplete, + Warnings: v7action.Warnings{"failed warning"}, + } + close(fakeStream) + }() + }) + + It("prints messages and warnings", func() { + Expect(testUI.Out).To(SatisfyAll( + Say(`Upgrading service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username), + Say(`\n`), + Say(`Waiting for the operation to complete\.\.\n`), + Say(`\n`), + Say(`Upgrade of service instance %s complete\.\n`, serviceInstanceName), + Say(`OK\n`), + )) + + Expect(testUI.Err).To(SatisfyAll( + Say("a warning"), + Say("poll warning"), + Say("failed warning"), + )) }) }) } diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index f3cddabd4f7..3c1defcdf79 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -3536,19 +3536,21 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - UpgradeManagedServiceInstanceStub func(string, string) (v7action.Warnings, error) + UpgradeManagedServiceInstanceStub func(string, string) (chan v7action.PollJobEvent, v7action.Warnings, error) upgradeManagedServiceInstanceMutex sync.RWMutex upgradeManagedServiceInstanceArgsForCall []struct { arg1 string arg2 string } upgradeManagedServiceInstanceReturns struct { - result1 v7action.Warnings - result2 error + result1 chan v7action.PollJobEvent + result2 v7action.Warnings + result3 error } upgradeManagedServiceInstanceReturnsOnCall map[int]struct { - result1 v7action.Warnings - result2 error + result1 chan v7action.PollJobEvent + result2 v7action.Warnings + result3 error } UploadBitsPackageStub func(resources.Package, []sharedaction.V3Resource, io.Reader, int64) (resources.Package, v7action.Warnings, error) uploadBitsPackageMutex sync.RWMutex @@ -18805,7 +18807,7 @@ func (fake *FakeActor) UpdateUserProvidedServiceInstanceReturnsOnCall(i int, res }{result1, result2} } -func (fake *FakeActor) UpgradeManagedServiceInstance(arg1 string, arg2 string) (v7action.Warnings, error) { +func (fake *FakeActor) UpgradeManagedServiceInstance(arg1 string, arg2 string) (chan v7action.PollJobEvent, v7action.Warnings, error) { fake.upgradeManagedServiceInstanceMutex.Lock() ret, specificReturn := fake.upgradeManagedServiceInstanceReturnsOnCall[len(fake.upgradeManagedServiceInstanceArgsForCall)] fake.upgradeManagedServiceInstanceArgsForCall = append(fake.upgradeManagedServiceInstanceArgsForCall, struct { @@ -18818,10 +18820,10 @@ func (fake *FakeActor) UpgradeManagedServiceInstance(arg1 string, arg2 string) ( return fake.UpgradeManagedServiceInstanceStub(arg1, arg2) } if specificReturn { - return ret.result1, ret.result2 + return ret.result1, ret.result2, ret.result3 } fakeReturns := fake.upgradeManagedServiceInstanceReturns - return fakeReturns.result1, fakeReturns.result2 + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 } func (fake *FakeActor) UpgradeManagedServiceInstanceCallCount() int { @@ -18830,7 +18832,7 @@ func (fake *FakeActor) UpgradeManagedServiceInstanceCallCount() int { return len(fake.upgradeManagedServiceInstanceArgsForCall) } -func (fake *FakeActor) UpgradeManagedServiceInstanceCalls(stub func(string, string) (v7action.Warnings, error)) { +func (fake *FakeActor) UpgradeManagedServiceInstanceCalls(stub func(string, string) (chan v7action.PollJobEvent, v7action.Warnings, error)) { fake.upgradeManagedServiceInstanceMutex.Lock() defer fake.upgradeManagedServiceInstanceMutex.Unlock() fake.UpgradeManagedServiceInstanceStub = stub @@ -18843,30 +18845,33 @@ func (fake *FakeActor) UpgradeManagedServiceInstanceArgsForCall(i int) (string, return argsForCall.arg1, argsForCall.arg2 } -func (fake *FakeActor) UpgradeManagedServiceInstanceReturns(result1 v7action.Warnings, result2 error) { +func (fake *FakeActor) UpgradeManagedServiceInstanceReturns(result1 chan v7action.PollJobEvent, result2 v7action.Warnings, result3 error) { fake.upgradeManagedServiceInstanceMutex.Lock() defer fake.upgradeManagedServiceInstanceMutex.Unlock() fake.UpgradeManagedServiceInstanceStub = nil fake.upgradeManagedServiceInstanceReturns = struct { - result1 v7action.Warnings - result2 error - }{result1, result2} + result1 chan v7action.PollJobEvent + result2 v7action.Warnings + result3 error + }{result1, result2, result3} } -func (fake *FakeActor) UpgradeManagedServiceInstanceReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { +func (fake *FakeActor) UpgradeManagedServiceInstanceReturnsOnCall(i int, result1 chan v7action.PollJobEvent, result2 v7action.Warnings, result3 error) { fake.upgradeManagedServiceInstanceMutex.Lock() defer fake.upgradeManagedServiceInstanceMutex.Unlock() fake.UpgradeManagedServiceInstanceStub = nil if fake.upgradeManagedServiceInstanceReturnsOnCall == nil { fake.upgradeManagedServiceInstanceReturnsOnCall = make(map[int]struct { - result1 v7action.Warnings - result2 error + result1 chan v7action.PollJobEvent + result2 v7action.Warnings + result3 error }) } fake.upgradeManagedServiceInstanceReturnsOnCall[i] = struct { - result1 v7action.Warnings - result2 error - }{result1, result2} + result1 chan v7action.PollJobEvent + result2 v7action.Warnings + result3 error + }{result1, result2, result3} } func (fake *FakeActor) UploadBitsPackage(arg1 resources.Package, arg2 []sharedaction.V3Resource, arg3 io.Reader, arg4 int64) (resources.Package, v7action.Warnings, error) { diff --git a/integration/v7/isolated/upgrade_service_command_test.go b/integration/v7/isolated/upgrade_service_command_test.go index 41f30093eda..bc1bd085850 100644 --- a/integration/v7/isolated/upgrade_service_command_test.go +++ b/integration/v7/isolated/upgrade_service_command_test.go @@ -24,6 +24,7 @@ var _ = Describe("upgrade-service command", func() { Say(`\s+cf upgrade-service SERVICE_INSTANCE`), Say(`OPTIONS:`), Say(`\s+--force, -f\s+Force upgrade without asking for confirmation`), + Say(`\s+--wait, -w\s+Wait for the operation to complete\n`), Say(`SEE ALSO:`), Say(`\s+services, update-service, update-user-provided-service`), ) @@ -107,7 +108,7 @@ var _ = Describe("upgrade-service command", func() { }) }) - When("the service instance exist", func() { + When("the service instance exists", func() { var broker *servicebrokerstub.ServiceBrokerStub BeforeEach(func() { @@ -151,7 +152,6 @@ var _ = Describe("upgrade-service command", func() { Expect(session.Out).To(Say("Upgrade in progress")) }) }) - }) }) }) From 116357f09e3cf48d95112b46b53ddd97ee2d4c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Diego=20Gonz=C3=A1lez?= <25335192+jdgonzaleza@users.noreply.github.com> Date: Wed, 14 Sep 2022 13:21:14 -0500 Subject: [PATCH 028/543] Revert "feat: add --wait flag to upgrade-service (#2285)" (#2317) This reverts commit 41ebecb29f603c571cd0d5b3a4aa448e91007e10 due to failing tests. --- actor/v7action/service_instance.go | 16 +-- actor/v7action/service_instance_test.go | 45 +++---- command/v7/actor.go | 2 +- command/v7/upgrade_service_command.go | 25 ++-- command/v7/upgrade_service_command_test.go | 118 +++--------------- command/v7/v7fakes/fake_actor.go | 43 +++---- .../isolated/upgrade_service_command_test.go | 4 +- 7 files changed, 69 insertions(+), 184 deletions(-) diff --git a/actor/v7action/service_instance.go b/actor/v7action/service_instance.go index b1fa9b68e44..7ac5c7bbd5c 100644 --- a/actor/v7action/service_instance.go +++ b/actor/v7action/service_instance.go @@ -146,13 +146,10 @@ func (actor Actor) UpdateManagedServiceInstance(params UpdateManagedServiceInsta return stream, Warnings(warnings), err } -func (actor Actor) UpgradeManagedServiceInstance(serviceInstanceName string, spaceGUID string) (chan PollJobEvent, Warnings, error) { - var ( - serviceInstance resources.ServiceInstance - servicePlan resources.ServicePlan - jobURL ccv3.JobURL - stream chan PollJobEvent - ) +func (actor Actor) UpgradeManagedServiceInstance(serviceInstanceName string, spaceGUID string) (Warnings, error) { + var serviceInstance resources.ServiceInstance + var servicePlan resources.ServicePlan + var jobURL ccv3.JobURL warnings, err := railway.Sequentially( func() (warnings ccv3.Warnings, err error) { @@ -176,12 +173,11 @@ func (actor Actor) UpgradeManagedServiceInstance(serviceInstanceName string, spa return }, func() (warnings ccv3.Warnings, err error) { - stream = actor.PollJobToEventStream(jobURL) - return + return actor.CloudControllerClient.PollJobForState(jobURL, constant.JobPolling) }, ) - return stream, Warnings(warnings), err + return Warnings(warnings), err } func (actor Actor) RenameServiceInstance(currentServiceInstanceName, spaceGUID, newServiceInstanceName string) (Warnings, error) { diff --git a/actor/v7action/service_instance_test.go b/actor/v7action/service_instance_test.go index 03a7ca94f6e..cb1551a3db1 100644 --- a/actor/v7action/service_instance_test.go +++ b/actor/v7action/service_instance_test.go @@ -493,7 +493,7 @@ var _ = Describe("Service Instance Actions", func() { }) It("returns warnings and an error", func() { - Expect(warnings).To(ContainElement("warning from get")) + //Expect(warnings).To(ContainElement("warning from get")) Expect(executeErr).To(MatchError("bang")) }) }) @@ -1282,13 +1282,12 @@ var _ = Describe("Service Instance Actions", func() { ) var ( - executeErr error - warnings Warnings - stream chan PollJobEvent + err error + warnings Warnings ) JustBeforeEach(func() { - stream, warnings, executeErr = actor.UpgradeManagedServiceInstance(fakeServiceInstanceName, fakeSpaceGUID) + warnings, err = actor.UpgradeManagedServiceInstance(fakeServiceInstanceName, fakeSpaceGUID) }) It("makes a request to get the service instance", func() { @@ -1314,9 +1313,8 @@ var _ = Describe("Service Instance Actions", func() { }) It("returns the appropriate error", func() { - Expect(stream).To(BeNil()) Expect(warnings).To(ConsistOf("get SI warning")) - Expect(executeErr).To(MatchError(actionerror.ServiceInstanceNotFoundError{ + Expect(err).To(MatchError(actionerror.ServiceInstanceNotFoundError{ Name: fakeServiceInstanceName, })) }) @@ -1344,9 +1342,8 @@ var _ = Describe("Service Instance Actions", func() { }) It("returns the appropriate error", func() { - Expect(stream).To(BeNil()) Expect(warnings).To(ConsistOf("get SI warning")) - Expect(executeErr).To(MatchError(actionerror.ServiceInstanceUpgradeNotAvailableError{})) + Expect(err).To(MatchError(actionerror.ServiceInstanceUpgradeNotAvailableError{})) }) }) @@ -1383,15 +1380,10 @@ var _ = Describe("Service Instance Actions", func() { ccv3.Warnings{"warning from update"}, nil, ) - - fakeStream := make(chan ccv3.PollJobEvent) - go func() { - fakeStream <- ccv3.PollJobEvent{ - State: constant.JobProcessing, - Warnings: ccv3.Warnings{"stream warning"}, - } - }() - fakeCloudControllerClient.PollJobToEventStreamReturns(fakeStream) + fakeCloudControllerClient.PollJobForStateReturns( + ccv3.Warnings{"warning from poll"}, + nil, + ) }) It("makes the right calls and returns all warnings", func() { @@ -1411,18 +1403,15 @@ var _ = Describe("Service Instance Actions", func() { }) By("polling the job", func() { - Expect(fakeCloudControllerClient.PollJobToEventStreamCallCount()).To(Equal(1)) - Expect(fakeCloudControllerClient.PollJobToEventStreamArgsForCall(0)).To(Equal(jobURL)) + Expect(fakeCloudControllerClient.PollJobForStateCallCount()).To(Equal(1)) + actualURL, actualState := fakeCloudControllerClient.PollJobForStateArgsForCall(0) + Expect(actualURL).To(Equal(jobURL)) + Expect(actualState).To(Equal(constant.JobPolling)) }) - By("returning a stream, warnings and no error", func() { - Eventually(stream).Should(Receive(Equal(PollJobEvent{ - State: JobProcessing, - Warnings: Warnings{"stream warning"}, - }))) - - Expect(executeErr).NotTo(HaveOccurred()) - Expect(warnings).To(ConsistOf("warning from get", "warning from plan", "warning from update")) + By("returning warnings and no error", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("warning from get", "warning from plan", "warning from update", "warning from poll")) }) }) }) diff --git a/command/v7/actor.go b/command/v7/actor.go index b6173934d63..3bbae507c69 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -237,7 +237,7 @@ type Actor interface { UpdateDestination(string, string, string) (v7action.Warnings, error) UpdateDomainLabelsByDomainName(string, map[string]types.NullString) (v7action.Warnings, error) UpdateManagedServiceInstance(params v7action.UpdateManagedServiceInstanceParams) (chan v7action.PollJobEvent, v7action.Warnings, error) - UpgradeManagedServiceInstance(serviceInstanceName, spaceGUID string) (chan v7action.PollJobEvent, v7action.Warnings, error) + UpgradeManagedServiceInstance(serviceInstanceName, spaceGUID string) (v7action.Warnings, error) UpdateOrganizationLabelsByOrganizationName(string, map[string]types.NullString) (v7action.Warnings, error) UpdateOrganizationQuota(quotaName string, newName string, limits v7action.QuotaLimits) (v7action.Warnings, error) UpdateProcessByTypeAndApplication(processType string, appGUID string, updatedProcess resources.Process) (v7action.Warnings, error) diff --git a/command/v7/upgrade_service_command.go b/command/v7/upgrade_service_command.go index 91952e09f64..81bd39a065a 100644 --- a/command/v7/upgrade_service_command.go +++ b/command/v7/upgrade_service_command.go @@ -4,7 +4,6 @@ import ( "code.cloudfoundry.org/cli/actor/actionerror" "code.cloudfoundry.org/cli/command/flag" "code.cloudfoundry.org/cli/command/translatableerror" - "code.cloudfoundry.org/cli/command/v7/shared" ) type UpgradeServiceCommand struct { @@ -12,7 +11,6 @@ type UpgradeServiceCommand struct { RequiredArgs flag.ServiceInstance `positional-args:"yes"` Force bool `short:"f" long:"force" description:"Force upgrade without asking for confirmation"` - Wait bool `short:"w" long:"wait" description:"Wait for the operation to complete"` relatedCommands interface{} `related_commands:"services, update-service, update-user-provided-service"` } @@ -40,7 +38,7 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { serviceInstanceName := string(cmd.RequiredArgs.ServiceInstance) - stream, warnings, actorError := cmd.Actor.UpgradeManagedServiceInstance( + warnings, actorError := cmd.Actor.UpgradeManagedServiceInstance( serviceInstanceName, cmd.Config.TargetedSpace().GUID, ) @@ -48,6 +46,8 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { switch actorError.(type) { case nil: + cmd.displayUpgradeInProgressMessage() + cmd.UI.DisplayOK() case actionerror.ServiceInstanceUpgradeNotAvailableError: cmd.UI.DisplayText(actorError.Error()) cmd.UI.DisplayOK() @@ -57,17 +57,6 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { return actorError } - complete, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) - switch { - case err != nil: - return err - case complete: - cmd.UI.DisplayTextWithFlavor("Upgrade of service instance {{.ServiceInstanceName}} complete.", cmd.serviceInstanceName()) - default: - cmd.UI.DisplayTextWithFlavor("Upgrade in progress. Use 'cf services' or 'cf service {{.ServiceInstanceName}}' to check operation status.", cmd.serviceInstanceName()) - } - - cmd.UI.DisplayOK() return nil } @@ -90,7 +79,6 @@ func (cmd UpgradeServiceCommand) displayEvent() error { "Username": user.Name, }, ) - cmd.UI.DisplayNewline() return nil } @@ -113,6 +101,13 @@ func (cmd UpgradeServiceCommand) displayPrompt() (bool, error) { return upgrade, nil } +func (cmd UpgradeServiceCommand) displayUpgradeInProgressMessage() { + cmd.UI.DisplayTextWithFlavor("Upgrade in progress. Use 'cf services' or 'cf service {{.ServiceInstance}}' to check operation status.", + map[string]interface{}{ + "ServiceInstance": cmd.RequiredArgs.ServiceInstance, + }) +} + func (cmd UpgradeServiceCommand) serviceInstanceName() map[string]interface{} { return map[string]interface{}{ "ServiceInstanceName": cmd.RequiredArgs.ServiceInstance, diff --git a/command/v7/upgrade_service_command_test.go b/command/v7/upgrade_service_command_test.go index 8bbd7d8dcfb..bc8aa3f54fa 100644 --- a/command/v7/upgrade_service_command_test.go +++ b/command/v7/upgrade_service_command_test.go @@ -76,7 +76,6 @@ var _ = Describe("upgrade-service command", func() { When("the service instance does not exist", func() { BeforeEach(func() { fakeActor.UpgradeManagedServiceInstanceReturns( - nil, v7action.Warnings{"upgrade warning"}, actionerror.ServiceInstanceNotFoundError{Name: serviceInstanceName}, ) @@ -90,126 +89,37 @@ var _ = Describe("upgrade-service command", func() { }) }) - When("the actor returns an unexpected error", func() { + When("the service instance upgrade starts successfully", func() { BeforeEach(func() { fakeActor.UpgradeManagedServiceInstanceReturns( - make(chan v7action.PollJobEvent), v7action.Warnings{"upgrade warning"}, - errors.New("bang"), - ) - }) - - It("fails with warnings", func() { - Expect(executeErr).To(MatchError("bang")) - Expect(testUI.Err).To(Say("upgrade warning")) - Expect(testUI.Out).NotTo(Say("OK")) - }) - }) - - When("stream goes to polling", func() { - BeforeEach(func() { - fakeStream := make(chan v7action.PollJobEvent) - fakeActor.UpgradeManagedServiceInstanceReturns( - fakeStream, - v7action.Warnings{"actor warning"}, nil, ) - - go func() { - fakeStream <- v7action.PollJobEvent{ - State: v7action.JobPolling, - Warnings: v7action.Warnings{"poll warning"}, - } - }() }) - It("prints messages and warnings", func() { + It("succeeds with a message", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Err).To(Say("upgrade warning")) Expect(testUI.Out).To(SatisfyAll( - Say(`Upgrading service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username), - Say(`\n`), - Say(`Upgrade in progress. Use 'cf services' or 'cf service %s' to check operation status\.`, serviceInstanceName), - Say(`OK\n`), - )) - - Expect(testUI.Err).To(SatisfyAll( - Say("actor warning"), - Say("poll warning"), + Say("\n"), + Say(`Upgrade in progress. Use 'cf services' or 'cf service %s' to check operation status\.\n`, serviceInstanceName), + Say("OK\n"), )) }) }) - When("error in event stream", func() { - BeforeEach(func() { - setFlag(&cmd, "--wait") - - fakeStream := make(chan v7action.PollJobEvent) - fakeActor.UpgradeManagedServiceInstanceReturns( - fakeStream, - v7action.Warnings{"a warning"}, - nil, - ) - - go func() { - fakeStream <- v7action.PollJobEvent{ - State: v7action.JobPolling, - Warnings: v7action.Warnings{"poll warning"}, - } - fakeStream <- v7action.PollJobEvent{ - State: v7action.JobFailed, - Warnings: v7action.Warnings{"failed warning"}, - Err: errors.New("boom"), - } - }() - }) - - It("returns the error and prints warnings", func() { - Expect(executeErr).To(MatchError("boom")) - Expect(testUI.Err).To(SatisfyAll( - Say("poll warning"), - Say("failed warning"), - )) - }) - }) - - When("--wait flag specified", func() { + When("the actor returns an unexpected error", func() { BeforeEach(func() { - setFlag(&cmd, "--wait") - - fakeStream := make(chan v7action.PollJobEvent) fakeActor.UpgradeManagedServiceInstanceReturns( - fakeStream, - v7action.Warnings{"a warning"}, - nil, + v7action.Warnings{"upgrade warning"}, + errors.New("bang"), ) - - go func() { - fakeStream <- v7action.PollJobEvent{ - State: v7action.JobPolling, - Warnings: v7action.Warnings{"poll warning"}, - } - fakeStream <- v7action.PollJobEvent{ - State: v7action.JobComplete, - Warnings: v7action.Warnings{"failed warning"}, - } - close(fakeStream) - }() }) - It("prints messages and warnings", func() { - Expect(testUI.Out).To(SatisfyAll( - Say(`Upgrading service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username), - Say(`\n`), - Say(`Waiting for the operation to complete\.\.\n`), - Say(`\n`), - Say(`Upgrade of service instance %s complete\.\n`, serviceInstanceName), - Say(`OK\n`), - )) - - Expect(testUI.Err).To(SatisfyAll( - Say("a warning"), - Say("poll warning"), - Say("failed warning"), - )) + It("fails with warnings", func() { + Expect(executeErr).To(MatchError("bang")) + Expect(testUI.Err).To(Say("upgrade warning")) + Expect(testUI.Out).NotTo(Say("OK")) }) }) } diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index 3c1defcdf79..f3cddabd4f7 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -3536,21 +3536,19 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - UpgradeManagedServiceInstanceStub func(string, string) (chan v7action.PollJobEvent, v7action.Warnings, error) + UpgradeManagedServiceInstanceStub func(string, string) (v7action.Warnings, error) upgradeManagedServiceInstanceMutex sync.RWMutex upgradeManagedServiceInstanceArgsForCall []struct { arg1 string arg2 string } upgradeManagedServiceInstanceReturns struct { - result1 chan v7action.PollJobEvent - result2 v7action.Warnings - result3 error + result1 v7action.Warnings + result2 error } upgradeManagedServiceInstanceReturnsOnCall map[int]struct { - result1 chan v7action.PollJobEvent - result2 v7action.Warnings - result3 error + result1 v7action.Warnings + result2 error } UploadBitsPackageStub func(resources.Package, []sharedaction.V3Resource, io.Reader, int64) (resources.Package, v7action.Warnings, error) uploadBitsPackageMutex sync.RWMutex @@ -18807,7 +18805,7 @@ func (fake *FakeActor) UpdateUserProvidedServiceInstanceReturnsOnCall(i int, res }{result1, result2} } -func (fake *FakeActor) UpgradeManagedServiceInstance(arg1 string, arg2 string) (chan v7action.PollJobEvent, v7action.Warnings, error) { +func (fake *FakeActor) UpgradeManagedServiceInstance(arg1 string, arg2 string) (v7action.Warnings, error) { fake.upgradeManagedServiceInstanceMutex.Lock() ret, specificReturn := fake.upgradeManagedServiceInstanceReturnsOnCall[len(fake.upgradeManagedServiceInstanceArgsForCall)] fake.upgradeManagedServiceInstanceArgsForCall = append(fake.upgradeManagedServiceInstanceArgsForCall, struct { @@ -18820,10 +18818,10 @@ func (fake *FakeActor) UpgradeManagedServiceInstance(arg1 string, arg2 string) ( return fake.UpgradeManagedServiceInstanceStub(arg1, arg2) } if specificReturn { - return ret.result1, ret.result2, ret.result3 + return ret.result1, ret.result2 } fakeReturns := fake.upgradeManagedServiceInstanceReturns - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 + return fakeReturns.result1, fakeReturns.result2 } func (fake *FakeActor) UpgradeManagedServiceInstanceCallCount() int { @@ -18832,7 +18830,7 @@ func (fake *FakeActor) UpgradeManagedServiceInstanceCallCount() int { return len(fake.upgradeManagedServiceInstanceArgsForCall) } -func (fake *FakeActor) UpgradeManagedServiceInstanceCalls(stub func(string, string) (chan v7action.PollJobEvent, v7action.Warnings, error)) { +func (fake *FakeActor) UpgradeManagedServiceInstanceCalls(stub func(string, string) (v7action.Warnings, error)) { fake.upgradeManagedServiceInstanceMutex.Lock() defer fake.upgradeManagedServiceInstanceMutex.Unlock() fake.UpgradeManagedServiceInstanceStub = stub @@ -18845,33 +18843,30 @@ func (fake *FakeActor) UpgradeManagedServiceInstanceArgsForCall(i int) (string, return argsForCall.arg1, argsForCall.arg2 } -func (fake *FakeActor) UpgradeManagedServiceInstanceReturns(result1 chan v7action.PollJobEvent, result2 v7action.Warnings, result3 error) { +func (fake *FakeActor) UpgradeManagedServiceInstanceReturns(result1 v7action.Warnings, result2 error) { fake.upgradeManagedServiceInstanceMutex.Lock() defer fake.upgradeManagedServiceInstanceMutex.Unlock() fake.UpgradeManagedServiceInstanceStub = nil fake.upgradeManagedServiceInstanceReturns = struct { - result1 chan v7action.PollJobEvent - result2 v7action.Warnings - result3 error - }{result1, result2, result3} + result1 v7action.Warnings + result2 error + }{result1, result2} } -func (fake *FakeActor) UpgradeManagedServiceInstanceReturnsOnCall(i int, result1 chan v7action.PollJobEvent, result2 v7action.Warnings, result3 error) { +func (fake *FakeActor) UpgradeManagedServiceInstanceReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { fake.upgradeManagedServiceInstanceMutex.Lock() defer fake.upgradeManagedServiceInstanceMutex.Unlock() fake.UpgradeManagedServiceInstanceStub = nil if fake.upgradeManagedServiceInstanceReturnsOnCall == nil { fake.upgradeManagedServiceInstanceReturnsOnCall = make(map[int]struct { - result1 chan v7action.PollJobEvent - result2 v7action.Warnings - result3 error + result1 v7action.Warnings + result2 error }) } fake.upgradeManagedServiceInstanceReturnsOnCall[i] = struct { - result1 chan v7action.PollJobEvent - result2 v7action.Warnings - result3 error - }{result1, result2, result3} + result1 v7action.Warnings + result2 error + }{result1, result2} } func (fake *FakeActor) UploadBitsPackage(arg1 resources.Package, arg2 []sharedaction.V3Resource, arg3 io.Reader, arg4 int64) (resources.Package, v7action.Warnings, error) { diff --git a/integration/v7/isolated/upgrade_service_command_test.go b/integration/v7/isolated/upgrade_service_command_test.go index bc1bd085850..41f30093eda 100644 --- a/integration/v7/isolated/upgrade_service_command_test.go +++ b/integration/v7/isolated/upgrade_service_command_test.go @@ -24,7 +24,6 @@ var _ = Describe("upgrade-service command", func() { Say(`\s+cf upgrade-service SERVICE_INSTANCE`), Say(`OPTIONS:`), Say(`\s+--force, -f\s+Force upgrade without asking for confirmation`), - Say(`\s+--wait, -w\s+Wait for the operation to complete\n`), Say(`SEE ALSO:`), Say(`\s+services, update-service, update-user-provided-service`), ) @@ -108,7 +107,7 @@ var _ = Describe("upgrade-service command", func() { }) }) - When("the service instance exists", func() { + When("the service instance exist", func() { var broker *servicebrokerstub.ServiceBrokerStub BeforeEach(func() { @@ -152,6 +151,7 @@ var _ = Describe("upgrade-service command", func() { Expect(session.Out).To(Say("Upgrade in progress")) }) }) + }) }) }) From c67c88ecb65a589307f5899683ea2f28ae746824 Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Tue, 20 Sep 2022 17:13:42 -0400 Subject: [PATCH 029/543] =?UTF-8?q?Use=20jq=20instead=20of=20grep=20-P=20f?= =?UTF-8?q?or=20finding=20users=20to=20delete=20during=20integrat=E2=80=A6?= =?UTF-8?q?=20(#2319)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use jq instead of grep -P for finding users to delete during integration cleanup * remove per page cause we're already iterating over a known set of pages --- bin/cleanup-integration | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/cleanup-integration b/bin/cleanup-integration index 3dd18f8c022..ba0c275bc52 100755 --- a/bin/cleanup-integration +++ b/bin/cleanup-integration @@ -62,11 +62,10 @@ for stack in $(cf stacks | awk '/INTEGRATION-STACK/ { print $1 }'); do cf curl -X DELETE "/v3/stacks/$(cf stack --guid $stack)" done -CF_USERS=$(cf curl /v3/users | grep -oP '(?<="total_results":)[^"]*' |grep -o '[0-9]\+') -USER_PAGES=$(( $CF_USERS / 50 + 1)) +USER_PAGES=$(cf curl /v3/users | jq -r .pagination.total_pages) for ((i=1; i<=${USER_PAGES}; i++)) ; do - cf curl "/v3/users?per_page=50&page=${i}" | \ + cf curl "/v3/users?&page=${i}" | \ jq -r .resources[].username | \ grep -i -e ^integration-user -e CATS- | \ xargs_func cf delete-user -f || echo From eb52f6e594731164aec91024a08ffebb3a8fd87b Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Tue, 20 Sep 2022 17:25:07 -0400 Subject: [PATCH 030/543] Allow user to rename app if buildpack is deleted (#2321) * Add new UpdateApplicationName functions for actor and client. Change RenameApplicationByNameAndSpaceGuid to use new UpdateApplicationName function. * Add UpdateApplicationName to cloud-controller-client interface. * create ApplicationNameOnly struct that marshalls correctly Co-authored-by: Ryker Reed --- actor/v7action/application.go | 15 ++- actor/v7action/application_test.go | 84 ++++++++++++++-- actor/v7action/cloud_controller_client.go | 1 + .../fake_cloud_controller_client.go | 85 ++++++++++++++++ api/cloudcontroller/ccv3/application.go | 13 +++ api/cloudcontroller/ccv3/application_test.go | 98 +++++++++++++++++++ resources/application_resource.go | 6 ++ 7 files changed, 291 insertions(+), 11 deletions(-) diff --git a/actor/v7action/application.go b/actor/v7action/application.go index 95f1abb4694..a7a8d6ea7c5 100644 --- a/actor/v7action/application.go +++ b/actor/v7action/application.go @@ -400,6 +400,17 @@ func (actor Actor) UpdateApplication(app resources.Application) (resources.Appli return updatedApp, Warnings(warnings), nil } +// UpdateApplicationName updates the name of an application +func (actor Actor) UpdateApplicationName(newAppName string, appGUID string) (resources.Application, Warnings, error) { + + updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplicationName(newAppName, appGUID) + if err != nil { + return resources.Application{}, Warnings(warnings), err + } + + return updatedApp, Warnings(warnings), nil +} + func (actor Actor) getDeployment(deploymentGUID string) (resources.Deployment, Warnings, error) { deployment, warnings, err := actor.CloudControllerClient.GetDeployment(deploymentGUID) if err != nil { @@ -444,8 +455,8 @@ func (actor Actor) RenameApplicationByNameAndSpaceGUID(appName, newAppName, spac if err != nil { return resources.Application{}, allWarnings, err } - application.Name = newAppName - application, warnings, err = actor.UpdateApplication(application) + appGUID := application.GUID + application, warnings, err = actor.UpdateApplicationName(newAppName, appGUID) allWarnings = append(allWarnings, warnings...) if err != nil { return resources.Application{}, allWarnings, err diff --git a/actor/v7action/application_test.go b/actor/v7action/application_test.go index 4d02f5c565c..e360fd5dd74 100644 --- a/actor/v7action/application_test.go +++ b/actor/v7action/application_test.go @@ -773,6 +773,76 @@ var _ = Describe("Application Actions", func() { }) }) + Describe("UpdateApplicationName", func() { + var ( + resultApp resources.Application + newAppName, appGUID string + warnings Warnings + err error + ) + + JustBeforeEach(func() { + newAppName = "some-new-app-name" + appGUID = "some-app-guid" + + resultApp, warnings, err = actor.UpdateApplicationName(newAppName, appGUID) + }) + + When("the app successfully gets updated", func() { + var apiResponseApp resources.Application + + BeforeEach(func() { + apiResponseApp = resources.Application{ + GUID: "response-app-guid", + StackName: "response-stack-name", + Name: "response-app-name", + LifecycleType: constant.AppLifecycleTypeBuildpack, + LifecycleBuildpacks: []string{"response-buildpack-1", "response-buildpack-2"}, + } + fakeCloudControllerClient.UpdateApplicationNameReturns( + apiResponseApp, + ccv3.Warnings{"some-warning"}, + nil, + ) + }) + + It("creates and returns the application and warnings", func() { + Expect(err).ToNot(HaveOccurred()) + Expect(resultApp).To(Equal(resources.Application{ + Name: apiResponseApp.Name, + GUID: apiResponseApp.GUID, + StackName: apiResponseApp.StackName, + LifecycleType: apiResponseApp.LifecycleType, + LifecycleBuildpacks: apiResponseApp.LifecycleBuildpacks, + })) + Expect(warnings).To(ConsistOf("some-warning")) + + Expect(fakeCloudControllerClient.UpdateApplicationNameCallCount()).To(Equal(1)) + appName, appGuid := fakeCloudControllerClient.UpdateApplicationNameArgsForCall(0) + Expect(appName).To(Equal("some-new-app-name")) + Expect(appGuid).To(Equal("some-app-guid")) + }) + }) + + When("the cc client returns an error", func() { + var expectedError error + + BeforeEach(func() { + expectedError = errors.New("I am a CloudControllerClient Error") + fakeCloudControllerClient.UpdateApplicationNameReturns( + resources.Application{}, + ccv3.Warnings{"some-warning"}, + expectedError, + ) + }) + + It("raises the error and warnings", func() { + Expect(err).To(MatchError(expectedError)) + Expect(warnings).To(ConsistOf("some-warning")) + }) + }) + }) + Describe("PollStart", func() { var ( app resources.Application @@ -2016,7 +2086,7 @@ var _ = Describe("Application Actions", func() { }, ccv3.Warnings{"get-app-warning"}, nil) - fakeCloudControllerClient.UpdateApplicationReturns( + fakeCloudControllerClient.UpdateApplicationNameReturns( resources.Application{}, ccv3.Warnings{"update-app-warning"}, expectedError) @@ -2042,7 +2112,7 @@ var _ = Describe("Application Actions", func() { nil, ) - fakeCloudControllerClient.UpdateApplicationReturns( + fakeCloudControllerClient.UpdateApplicationNameReturns( resources.Application{ Name: "new-app-name", GUID: "old-app-guid", @@ -2060,13 +2130,9 @@ var _ = Describe("Application Actions", func() { GUID: "old-app-guid", })) Expect(warnings).To(ConsistOf("get-app-warning", "update-app-warning")) - - Expect(fakeCloudControllerClient.UpdateApplicationArgsForCall(0)).To(Equal( - resources.Application{ - Name: "new-app-name", - GUID: "old-app-guid", - })) - + appName, appGuid := fakeCloudControllerClient.UpdateApplicationNameArgsForCall(0) + Expect(appName).To(Equal("new-app-name")) + Expect(appGuid).To(Equal("old-app-guid")) }) }) diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index 050b4d5fdac..e1f889cf7ad 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -159,6 +159,7 @@ type CloudControllerClient interface { UnshareServiceInstanceFromSpace(serviceInstanceGUID string, sharedToSpaceGUID string) (ccv3.Warnings, error) UpdateAppFeature(appGUID string, enabled bool, featureName string) (ccv3.Warnings, error) UpdateApplication(app resources.Application) (resources.Application, ccv3.Warnings, error) + UpdateApplicationName(newAppName string, appGUID string) (resources.Application, ccv3.Warnings, error) UpdateApplicationApplyManifest(appGUID string, rawManifest []byte) (ccv3.JobURL, ccv3.Warnings, error) UpdateApplicationEnvironmentVariables(appGUID string, envVars resources.EnvironmentVariables) (resources.EnvironmentVariables, ccv3.Warnings, error) UpdateApplicationRestart(appGUID string) (resources.Application, ccv3.Warnings, error) diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 8b2d4240737..5511b96e84a 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -2271,6 +2271,22 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + UpdateApplicationNameStub func(string, string) (resources.Application, ccv3.Warnings, error) + updateApplicationNameMutex sync.RWMutex + updateApplicationNameArgsForCall []struct { + arg1 string + arg2 string + } + updateApplicationNameReturns struct { + result1 resources.Application + result2 ccv3.Warnings + result3 error + } + updateApplicationNameReturnsOnCall map[int]struct { + result1 resources.Application + result2 ccv3.Warnings + result3 error + } UpdateApplicationRestartStub func(string) (resources.Application, ccv3.Warnings, error) updateApplicationRestartMutex sync.RWMutex updateApplicationRestartArgsForCall []struct { @@ -12601,6 +12617,73 @@ func (fake *FakeCloudControllerClient) UpdateApplicationEnvironmentVariablesRetu }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) UpdateApplicationName(arg1 string, arg2 string) (resources.Application, ccv3.Warnings, error) { + fake.updateApplicationNameMutex.Lock() + ret, specificReturn := fake.updateApplicationNameReturnsOnCall[len(fake.updateApplicationNameArgsForCall)] + fake.updateApplicationNameArgsForCall = append(fake.updateApplicationNameArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("UpdateApplicationName", []interface{}{arg1, arg2}) + fake.updateApplicationNameMutex.Unlock() + if fake.UpdateApplicationNameStub != nil { + return fake.UpdateApplicationNameStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + fakeReturns := fake.updateApplicationNameReturns + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeCloudControllerClient) UpdateApplicationNameCallCount() int { + fake.updateApplicationNameMutex.RLock() + defer fake.updateApplicationNameMutex.RUnlock() + return len(fake.updateApplicationNameArgsForCall) +} + +func (fake *FakeCloudControllerClient) UpdateApplicationNameCalls(stub func(string, string) (resources.Application, ccv3.Warnings, error)) { + fake.updateApplicationNameMutex.Lock() + defer fake.updateApplicationNameMutex.Unlock() + fake.UpdateApplicationNameStub = stub +} + +func (fake *FakeCloudControllerClient) UpdateApplicationNameArgsForCall(i int) (string, string) { + fake.updateApplicationNameMutex.RLock() + defer fake.updateApplicationNameMutex.RUnlock() + argsForCall := fake.updateApplicationNameArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCloudControllerClient) UpdateApplicationNameReturns(result1 resources.Application, result2 ccv3.Warnings, result3 error) { + fake.updateApplicationNameMutex.Lock() + defer fake.updateApplicationNameMutex.Unlock() + fake.UpdateApplicationNameStub = nil + fake.updateApplicationNameReturns = struct { + result1 resources.Application + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeCloudControllerClient) UpdateApplicationNameReturnsOnCall(i int, result1 resources.Application, result2 ccv3.Warnings, result3 error) { + fake.updateApplicationNameMutex.Lock() + defer fake.updateApplicationNameMutex.Unlock() + fake.UpdateApplicationNameStub = nil + if fake.updateApplicationNameReturnsOnCall == nil { + fake.updateApplicationNameReturnsOnCall = make(map[int]struct { + result1 resources.Application + result2 ccv3.Warnings + result3 error + }) + } + fake.updateApplicationNameReturnsOnCall[i] = struct { + result1 resources.Application + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeCloudControllerClient) UpdateApplicationRestart(arg1 string) (resources.Application, ccv3.Warnings, error) { fake.updateApplicationRestartMutex.Lock() ret, specificReturn := fake.updateApplicationRestartReturnsOnCall[len(fake.updateApplicationRestartArgsForCall)] @@ -14843,6 +14926,8 @@ func (fake *FakeCloudControllerClient) Invocations() map[string][][]interface{} defer fake.updateApplicationApplyManifestMutex.RUnlock() fake.updateApplicationEnvironmentVariablesMutex.RLock() defer fake.updateApplicationEnvironmentVariablesMutex.RUnlock() + fake.updateApplicationNameMutex.RLock() + defer fake.updateApplicationNameMutex.RUnlock() fake.updateApplicationRestartMutex.RLock() defer fake.updateApplicationRestartMutex.RUnlock() fake.updateApplicationStartMutex.RLock() diff --git a/api/cloudcontroller/ccv3/application.go b/api/cloudcontroller/ccv3/application.go index a060993253b..d4e7d56e0e2 100644 --- a/api/cloudcontroller/ccv3/application.go +++ b/api/cloudcontroller/ccv3/application.go @@ -66,6 +66,19 @@ func (client *Client) UpdateApplication(app resources.Application) (resources.Ap return responseBody, warnings, err } +// UpdateApplicationName updates an application with the new name given +func (client *Client) UpdateApplicationName(newAppName string, appGUID string) (resources.Application, Warnings, error) { + var responseBody resources.Application + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.PatchApplicationRequest, + URIParams: internal.Params{"app_guid": appGUID}, + RequestBody: resources.ApplicationNameOnly{Name: newAppName}, + ResponseBody: &responseBody, + }) + + return responseBody, warnings, err +} + // UpdateApplicationRestart restarts the given application. func (client *Client) UpdateApplicationRestart(appGUID string) (resources.Application, Warnings, error) { var responseBody resources.Application diff --git a/api/cloudcontroller/ccv3/application_test.go b/api/cloudcontroller/ccv3/application_test.go index ff2bf9e4d4d..2e1ec880ffd 100644 --- a/api/cloudcontroller/ccv3/application_test.go +++ b/api/cloudcontroller/ccv3/application_test.go @@ -614,6 +614,104 @@ var _ = Describe("Application", func() { }) }) + Describe("UpdateApplicationName", func() { + var ( + newAppName string + appGUID string + + updatedApp resources.Application + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + newAppName = "some-new-app-name" + appGUID = "some-app-guid" + + updatedApp, warnings, executeErr = client.UpdateApplicationName(newAppName, appGUID) + }) + + When("the application successfully is updated", func() { + BeforeEach(func() { + requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) { + requestParams.ResponseBody.(*resources.Application).GUID = appGUID + requestParams.ResponseBody.(*resources.Application).Name = requestParams.RequestBody.(resources.ApplicationNameOnly).Name + requestParams.ResponseBody.(*resources.Application).StackName = "some-stack-name" + requestParams.ResponseBody.(*resources.Application).LifecycleType = constant.AppLifecycleTypeBuildpack + requestParams.ResponseBody.(*resources.Application).LifecycleBuildpacks = []string{"some-buildpack"} + requestParams.ResponseBody.(*resources.Application).SpaceGUID = "some-space-guid" + return "", Warnings{"this is a warning"}, nil + }) + }) + + It("makes the correct request", func() { + Expect(requester.MakeRequestCallCount()).To(Equal(1)) + actualParams := requester.MakeRequestArgsForCall(0) + Expect(actualParams.RequestName).To(Equal(internal.PatchApplicationRequest)) + Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"})) + Expect(actualParams.RequestBody).To(Equal(resources.ApplicationNameOnly{Name: newAppName})) + _, ok := actualParams.ResponseBody.(*resources.Application) + Expect(ok).To(BeTrue()) + }) + + It("returns the updated app and warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("this is a warning")) + + Expect(updatedApp).To(Equal(resources.Application{ + GUID: "some-app-guid", + StackName: "some-stack-name", + LifecycleBuildpacks: []string{"some-buildpack"}, + LifecycleType: constant.AppLifecycleTypeBuildpack, + Name: "some-new-app-name", + SpaceGUID: "some-space-guid", + })) + }) + }) + + When("cc returns back an error or warnings", func() { + BeforeEach(func() { + errors := []ccerror.V3Error{ + { + Code: 10008, + Detail: "The request is semantically invalid: command presence", + Title: "CF-UnprocessableEntity", + }, + { + Code: 10010, + Detail: "App not found", + Title: "CF-ResourceNotFound", + }, + } + + requester.MakeRequestReturns( + "", + Warnings{"this is a warning"}, + ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors}, + ) + }) + + It("returns the error and all warnings", func() { + Expect(executeErr).To(MatchError(ccerror.MultiError{ + ResponseCode: http.StatusTeapot, + Errors: []ccerror.V3Error{ + { + Code: 10008, + Detail: "The request is semantically invalid: command presence", + Title: "CF-UnprocessableEntity", + }, + { + Code: 10010, + Detail: "App not found", + Title: "CF-ResourceNotFound", + }, + }, + })) + Expect(warnings).To(ConsistOf("this is a warning")) + }) + }) + }) + Describe("UpdateApplicationStop", func() { var ( responseApp resources.Application diff --git a/resources/application_resource.go b/resources/application_resource.go index 00d0a0b95f8..b7b5c782557 100644 --- a/resources/application_resource.go +++ b/resources/application_resource.go @@ -27,6 +27,12 @@ type Application struct { State constant.ApplicationState } +// ApplicationNameOnly represents only the name field of a Cloud Controller V3 Application +type ApplicationNameOnly struct { + // Name is the name given to the application. + Name string `json:"name,omitempty"` +} + // MarshalJSON converts an Application into a Cloud Controller Application. func (a Application) MarshalJSON() ([]byte, error) { ccApp := ccApplication{ From ead1418e259a446ab63a5f16670d525d989031e1 Mon Sep 17 00:00:00 2001 From: Georgi Ivanov Date: Tue, 31 May 2022 20:00:56 +0300 Subject: [PATCH 031/543] add option to build for linux rm64 --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index aa70bbe8001..06096d29256 100644 --- a/Makefile +++ b/Makefile @@ -170,6 +170,11 @@ out/cf-cli_linux_x86-64: $(GOSRC) CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \ $(REQUIRED_FOR_STATIC_BINARY) \ -ldflags "$(LD_FLAGS_LINUX)" -o out/cf-cli_linux_x86-64 . + +out/cf-cli_linux_arm64: $(GOSRC) + CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build \ + $(REQUIRED_FOR_STATIC_BINARY) \ + -ldflags "$(LD_FLAGS_LINUX)" -o out/cf-cli_linux_arm64 . out/cf-cli_osx: $(GOSRC) GOARCH=amd64 GOOS=darwin go build \ From a4ae5d18d028d125433c446427075c127e3e9034 Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Thu, 22 Sep 2022 14:35:11 -0400 Subject: [PATCH 032/543] Use Eventually/Should for routes_command list routes with http1 and (#2324) - Existing test was flaky as the order of the two lines could change. This uses eventually/should to check the buffer in any order --- integration/v7/isolated/routes_command_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/v7/isolated/routes_command_test.go b/integration/v7/isolated/routes_command_test.go index 73813867368..0e1c302762f 100644 --- a/integration/v7/isolated/routes_command_test.go +++ b/integration/v7/isolated/routes_command_test.go @@ -186,8 +186,8 @@ var _ = Describe("routes command", func() { Eventually(session).Should(Exit(0)) Expect(session).To(Say(`Getting routes for org %s / space %s as %s\.\.\.`, orgName, spaceName, userName)) Expect(session).To(Say(tableHeaders)) - Expect(session).To(Say(`%s\s+route1\s+%s\s+http\s+http1, http2\s+%s\s+\n`, spaceName, domainName, fmt.Sprintf("%s, %s", appName1, appName2))) - Expect(session).To(Say(`%s\s+route2\s+%s\s+http\s+http1\s+%s\s+\n`, spaceName, domainName, appName2)) + Eventually(session).Should(Say(`%s\s+route1\s+%s\s+http\s+http1, http2\s+%s\s+\n`, spaceName, domainName, fmt.Sprintf("%s, %s", appName1, appName2))) + Eventually(session).Should(Say(`%s\s+route2\s+%s\s+http\s+http1\s+%s\s+\n`, spaceName, domainName, appName2)) }) }) From a130cc55b5e8f5cdabedf955f46c800b4f240cc7 Mon Sep 17 00:00:00 2001 From: Andrew Crump Date: Tue, 13 Sep 2022 00:46:59 +0000 Subject: [PATCH 033/543] Return error for log rate limits larger than math.MaxInt This fix prevents the CLI from accepting a number that would overflow for the log rate limit. Co-authored-by: Rebecca Roberts --- command/flag/bytes_with_unlimited.go | 11 ++++++----- command/flag/bytes_with_unlimited_test.go | 12 +++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/command/flag/bytes_with_unlimited.go b/command/flag/bytes_with_unlimited.go index 59becfb3394..544378fdf4c 100644 --- a/command/flag/bytes_with_unlimited.go +++ b/command/flag/bytes_with_unlimited.go @@ -1,6 +1,7 @@ package flag import ( + "math" "regexp" "strings" @@ -36,7 +37,7 @@ func (m *BytesWithUnlimited) UnmarshalFlag(val string) error { return err } - m.Value = int(size) + m.Value = size m.IsSet = true return nil @@ -46,14 +47,14 @@ func (m *BytesWithUnlimited) IsValidValue(val string) error { return m.UnmarshalFlag(val) } -func ConvertToBytes(val string) (uint64, error) { +func ConvertToBytes(val string) (int, error) { size, err := bytefmt.ToBytes(val) - if err != nil || strings.Contains(strings.ToLower(val), ".") { - return size, &flags.Error{ + if err != nil || strings.Contains(strings.ToLower(val), ".") || size > math.MaxInt { + return 0, &flags.Error{ Type: flags.ErrRequired, Message: `Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB`, } } - return size, nil + return int(size), nil } diff --git a/command/flag/bytes_with_unlimited_test.go b/command/flag/bytes_with_unlimited_test.go index 29cf00c2d79..b200a6f40bb 100644 --- a/command/flag/bytes_with_unlimited_test.go +++ b/command/flag/bytes_with_unlimited_test.go @@ -86,7 +86,7 @@ var _ = Describe("BytesWithUnlimited", func() { }) When("the value is 0", func() { - It("set's the value to 0", func() { + It("sets the value to 0", func() { err := bytesWithUnlimited.UnmarshalFlag("0") Expect(err).ToNot(HaveOccurred()) Expect(bytesWithUnlimited.Value).To(BeEquivalentTo(0)) @@ -127,6 +127,16 @@ var _ = Describe("BytesWithUnlimited", func() { }) }) + When("the value is too large", func() { + It("returns an error", func() { + err := bytesWithUnlimited.UnmarshalFlag("9999999TB") + Expect(err).To(MatchError(&flags.Error{ + Type: flags.ErrRequired, + Message: `Byte quantity must be an integer with a unit of measurement like B, K, KB, M, MB, G, or GB`, + })) + }) + }) + When("the units are missing", func() { It("returns an error", func() { err := bytesWithUnlimited.UnmarshalFlag("37") From 4c97dfe27e73611fe1bc65a3c3ff3a8d5a9c3419 Mon Sep 17 00:00:00 2001 From: Merric de Launey Date: Tue, 11 Oct 2022 00:03:07 +0000 Subject: [PATCH 034/543] WIP: Implement unshare route command - unit tests pass - failing integration tests Co-authored-by: Merric de Launey Co-authored-by: David Alvarado --- actor/v7action/cloud_controller_client.go | 1 + actor/v7action/route.go | 5 + actor/v7action/route_test.go | 40 +++ .../fake_cloud_controller_client.go | 80 +++++ .../ccv3/internal/api_routes.go | 2 + api/cloudcontroller/ccv3/route.go | 11 + api/cloudcontroller/ccv3/route_test.go | 78 +++++ command/common/command_list_v7.go | 1 + command/common/internal/help_all_display.go | 2 +- command/v7/actor.go | 1 + command/v7/share_route_command.go | 2 +- command/v7/unshare_route_command.go | 101 +++++++ command/v7/unshare_route_command_test.go | 280 ++++++++++++++++++ command/v7/v7fakes/fake_actor.go | 80 +++++ .../v7/isolated/unshare_route_command_test.go | 208 +++++++++++++ 15 files changed, 890 insertions(+), 2 deletions(-) create mode 100644 command/v7/unshare_route_command.go create mode 100644 command/v7/unshare_route_command_test.go create mode 100644 integration/v7/isolated/unshare_route_command_test.go diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index e1f889cf7ad..e855e9624f5 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -155,6 +155,7 @@ type CloudControllerClient interface { UnbindSecurityGroupRunningSpace(securityGroupGUID string, spaceGUID string) (ccv3.Warnings, error) UnbindSecurityGroupStagingSpace(securityGroupGUID string, spaceGUID string) (ccv3.Warnings, error) UnmapRoute(routeGUID string, destinationGUID string) (ccv3.Warnings, error) + UnshareRoute(routeGUID string, spaceGUID string) (ccv3.Warnings, error) UnsharePrivateDomainFromOrg(domainGUID string, sharedOrgGUID string) (ccv3.Warnings, error) UnshareServiceInstanceFromSpace(serviceInstanceGUID string, sharedToSpaceGUID string) (ccv3.Warnings, error) UpdateAppFeature(appGUID string, enabled bool, featureName string) (ccv3.Warnings, error) diff --git a/actor/v7action/route.go b/actor/v7action/route.go index 7cac6180001..e3da624d425 100644 --- a/actor/v7action/route.go +++ b/actor/v7action/route.go @@ -410,6 +410,11 @@ func (actor Actor) ShareRoute(routeGUID string, spaceGUID string) (Warnings, err warnings, err := actor.CloudControllerClient.ShareRoute(routeGUID, spaceGUID) return Warnings(warnings), err } + +func (actor Actor) UnshareRoute(routeGUID string, spaceGUID string) (Warnings, error) { + warnings, err := actor.CloudControllerClient.UnshareRoute(routeGUID, spaceGUID) + return Warnings(warnings), err +} func (actor Actor) GetApplicationRoutes(appGUID string) ([]resources.Route, Warnings, error) { allWarnings := Warnings{} diff --git a/actor/v7action/route_test.go b/actor/v7action/route_test.go index 56381a0032f..3feacd6e5ba 100644 --- a/actor/v7action/route_test.go +++ b/actor/v7action/route_test.go @@ -1590,6 +1590,46 @@ var _ = Describe("Route Actions", func() { }) }) + Describe("UnshareRoute", func() { + var ( + routeGUID string + spaceGUID string + + executeErr error + warnings Warnings + ) + + JustBeforeEach(func() { + warnings, executeErr = actor.UnshareRoute(routeGUID, spaceGUID) + }) + + BeforeEach(func() { + routeGUID = "route-guid" + spaceGUID = "space-guid" + }) + + When("the cloud controller client errors", func() { + BeforeEach(func() { + fakeCloudControllerClient.UnshareRouteReturns(ccv3.Warnings{"unshare-route-warning"}, errors.New("unshare-route-error")) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError(errors.New("unshare-route-error"))) + Expect(warnings).To(ConsistOf("unshare-route-warning")) + }) + }) + + When("the cloud controller client succeeds", func() { + BeforeEach(func() { + fakeCloudControllerClient.UnshareRouteReturns(ccv3.Warnings{"unshare-route-warning"}, nil) + }) + + It("returns the warnings", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(warnings).To(ConsistOf("unshare-route-warning")) + }) + }) + }) Describe("UnmapRoute", func() { var ( routeGUID string diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 5511b96e84a..e2ff9883854 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -2195,6 +2195,20 @@ type FakeCloudControllerClient struct { result1 ccv3.Warnings result2 error } + UnshareRouteStub func(string, string) (ccv3.Warnings, error) + unshareRouteMutex sync.RWMutex + unshareRouteArgsForCall []struct { + arg1 string + arg2 string + } + unshareRouteReturns struct { + result1 ccv3.Warnings + result2 error + } + unshareRouteReturnsOnCall map[int]struct { + result1 ccv3.Warnings + result2 error + } UnshareServiceInstanceFromSpaceStub func(string, string) (ccv3.Warnings, error) unshareServiceInstanceFromSpaceMutex sync.RWMutex unshareServiceInstanceFromSpaceArgsForCall []struct { @@ -12283,6 +12297,70 @@ func (fake *FakeCloudControllerClient) UnsharePrivateDomainFromOrgReturnsOnCall( }{result1, result2} } +func (fake *FakeCloudControllerClient) UnshareRoute(arg1 string, arg2 string) (ccv3.Warnings, error) { + fake.unshareRouteMutex.Lock() + ret, specificReturn := fake.unshareRouteReturnsOnCall[len(fake.unshareRouteArgsForCall)] + fake.unshareRouteArgsForCall = append(fake.unshareRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("UnshareRoute", []interface{}{arg1, arg2}) + fake.unshareRouteMutex.Unlock() + if fake.UnshareRouteStub != nil { + return fake.UnshareRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.unshareRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeCloudControllerClient) UnshareRouteCallCount() int { + fake.unshareRouteMutex.RLock() + defer fake.unshareRouteMutex.RUnlock() + return len(fake.unshareRouteArgsForCall) +} + +func (fake *FakeCloudControllerClient) UnshareRouteCalls(stub func(string, string) (ccv3.Warnings, error)) { + fake.unshareRouteMutex.Lock() + defer fake.unshareRouteMutex.Unlock() + fake.UnshareRouteStub = stub +} + +func (fake *FakeCloudControllerClient) UnshareRouteArgsForCall(i int) (string, string) { + fake.unshareRouteMutex.RLock() + defer fake.unshareRouteMutex.RUnlock() + argsForCall := fake.unshareRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCloudControllerClient) UnshareRouteReturns(result1 ccv3.Warnings, result2 error) { + fake.unshareRouteMutex.Lock() + defer fake.unshareRouteMutex.Unlock() + fake.UnshareRouteStub = nil + fake.unshareRouteReturns = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeCloudControllerClient) UnshareRouteReturnsOnCall(i int, result1 ccv3.Warnings, result2 error) { + fake.unshareRouteMutex.Lock() + defer fake.unshareRouteMutex.Unlock() + fake.UnshareRouteStub = nil + if fake.unshareRouteReturnsOnCall == nil { + fake.unshareRouteReturnsOnCall = make(map[int]struct { + result1 ccv3.Warnings + result2 error + }) + } + fake.unshareRouteReturnsOnCall[i] = struct { + result1 ccv3.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeCloudControllerClient) UnshareServiceInstanceFromSpace(arg1 string, arg2 string) (ccv3.Warnings, error) { fake.unshareServiceInstanceFromSpaceMutex.Lock() ret, specificReturn := fake.unshareServiceInstanceFromSpaceReturnsOnCall[len(fake.unshareServiceInstanceFromSpaceArgsForCall)] @@ -14916,6 +14994,8 @@ func (fake *FakeCloudControllerClient) Invocations() map[string][][]interface{} defer fake.unsetSpaceQuotaMutex.RUnlock() fake.unsharePrivateDomainFromOrgMutex.RLock() defer fake.unsharePrivateDomainFromOrgMutex.RUnlock() + fake.unshareRouteMutex.RLock() + defer fake.unshareRouteMutex.RUnlock() fake.unshareServiceInstanceFromSpaceMutex.RLock() defer fake.unshareServiceInstanceFromSpaceMutex.RUnlock() fake.updateAppFeatureMutex.RLock() diff --git a/api/cloudcontroller/ccv3/internal/api_routes.go b/api/cloudcontroller/ccv3/internal/api_routes.go index 5da806e7d29..1e747e21033 100644 --- a/api/cloudcontroller/ccv3/internal/api_routes.go +++ b/api/cloudcontroller/ccv3/internal/api_routes.go @@ -175,6 +175,7 @@ const ( SharePrivateDomainRequest = "SharePrivateDomainRequest" ShareRouteRequest = "ShareRouteRequest" UnmapRouteRequest = "UnmapRoute" + UnshareRouteRequest = "UnshareRoute" WhoAmI = "WhoAmI" ) @@ -280,6 +281,7 @@ var APIRoutes = map[string]Route{ UnmapRouteRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodDelete}, PatchDestinationRequest: {Path: "/v3/routes/:route_guid/destinations/:destination_guid", Method: http.MethodPatch}, ShareRouteRequest: {Path: "/v3/routes/:route_guid/relationships/shared_spaces", Method: http.MethodPost}, + UnshareRouteRequest: {Path: "/v3/routes/:route_guid/relationships/shared_spaces/:space_guid", Method: http.MethodDelete}, PatchMoveRouteRequest: {Path: "/v3/routes/:route_guid/relationships/space", Method: http.MethodPatch}, GetSecurityGroupsRequest: {Path: "/v3/security_groups", Method: http.MethodGet}, PostSecurityGroupRequest: {Path: "/v3/security_groups", Method: http.MethodPost}, diff --git a/api/cloudcontroller/ccv3/route.go b/api/cloudcontroller/ccv3/route.go index 56f068640a6..afff0af5e9a 100644 --- a/api/cloudcontroller/ccv3/route.go +++ b/api/cloudcontroller/ccv3/route.go @@ -132,6 +132,17 @@ func (client Client) UnmapRoute(routeGUID string, destinationGUID string) (Warni return warnings, err } +func (client Client) UnshareRoute(routeGUID string, spaceGUID string) (Warnings, error) { + var responseBody resources.Build + + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.UnshareRouteRequest, + URIParams: internal.Params{"route_guid": routeGUID, "space_guid": spaceGUID}, + ResponseBody: &responseBody, + }) + return warnings, err +} + func (client Client) UpdateDestination(routeGUID string, destinationGUID string, protocol string) (Warnings, error) { type body struct { Protocol string `json:"protocol"` diff --git a/api/cloudcontroller/ccv3/route_test.go b/api/cloudcontroller/ccv3/route_test.go index 1ef7b6d2c5b..3e6f5e9a0da 100644 --- a/api/cloudcontroller/ccv3/route_test.go +++ b/api/cloudcontroller/ccv3/route_test.go @@ -798,6 +798,84 @@ var _ = Describe("Route", func() { }) }) + Describe("UnshareRoute", func() { + var ( + routeGUID string + spaceGUID string + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + warnings, executeErr = client.UnshareRoute(routeGUID, spaceGUID) + }) + + When("aroute exists and is shared with the space", func() { + routeGUID = "route-guid" + spaceGUID = "space-guid" + + BeforeEach(func() { + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodDelete, "/v3/routes/route-guid/relationships/shared_spaces/space-guid"), + RespondWith(http.StatusNoContent, nil, http.Header{ + "X-Cf-Warnings": {"this is a warning"}, + }), + ), + ) + }) + + It("returns warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("this is a warning")) + }) + }) + + When("the cloud controller returns errors and warnings", func() { + BeforeEach(func() { + response := `{ + "errors": [ + { + "code": 10008, + "detail": "The request is semantically invalid: command presence", + "title": "CF-UnprocessableEntity" + }, + { + "code": 10010, + "detail": "Route not found", + "title": "CF-ResourceNotFound" + } + ] + }` + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodDelete, "/v3/routes/route-guid/relationships/shared_spaces/space-guid"), + RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), + ), + ) + }) + + It("returns the error and all warnings", func() { + Expect(executeErr).To(MatchError(ccerror.MultiError{ + ResponseCode: http.StatusTeapot, + Errors: []ccerror.V3Error{ + { + Code: 10008, + Detail: "The request is semantically invalid: command presence", + Title: "CF-UnprocessableEntity", + }, + { + Code: 10010, + Detail: "Route not found", + Title: "CF-ResourceNotFound", + }, + }, + })) + Expect(warnings).To(ConsistOf("this is a warning")) + }) + }) + }) + Describe("UpdateDestination", func() { var ( routeGUID string diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index 3a2b660f3bd..f0a1de1c307 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -187,6 +187,7 @@ type commandList struct { UnsetSpaceQuota v7.UnsetSpaceQuotaCommand `command:"unset-space-quota" description:"Unassign a quota from a space"` UnsetSpaceRole v7.UnsetSpaceRoleCommand `command:"unset-space-role" description:"Remove a space role from a user"` UnsharePrivateDomain v7.UnsharePrivateDomainCommand `command:"unshare-private-domain" description:"Unshare a private domain with a specific org"` + UnshareRoute v7.UnshareRouteCommand `command:"unshare-route" description:"Unshare a shared route from a space"` UnshareService v7.UnshareServiceCommand `command:"unshare-service" description:"Unshare a shared service instance from a space"` UpdateBuildpack v7.UpdateBuildpackCommand `command:"update-buildpack" description:"Update a buildpack"` UpdateDestination v7.UpdateDestinationCommand `command:"update-destination" description:"Updates the destination protocol for a route"` diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 97990a5002d..738f496672e 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -68,7 +68,7 @@ var HelpCategoryList = []HelpCategory{ {"create-route", "check-route", "map-route", "unmap-route", "delete-route"}, {"delete-orphaned-routes"}, {"update-destination"}, - {"share-route"}, + {"share-route", "unshare-route"}, {"move-route"}, }, }, diff --git a/command/v7/actor.go b/command/v7/actor.go index 3bbae507c69..5559246c591 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -228,6 +228,7 @@ type Actor interface { UnsetEnvironmentVariableByApplicationNameAndSpace(appName string, spaceGUID string, EnvironmentVariableName string) (v7action.Warnings, error) UnsetSpaceQuota(spaceQuotaName, spaceName, orgGUID string) (v7action.Warnings, error) UnsharePrivateDomain(domainName string, orgName string) (v7action.Warnings, error) + UnshareRoute(routeGUID string, spaceGUID string) (v7action.Warnings, error) UnshareServiceInstanceFromSpaceAndOrg(serviceInstanceName, targetedSpaceGUID, targetedOrgGUID string, unshareFromDetails v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) UpdateAppFeature(app resources.Application, enabled bool, featureName string) (v7action.Warnings, error) UpdateApplication(app resources.Application) (resources.Application, v7action.Warnings, error) diff --git a/command/v7/share_route_command.go b/command/v7/share_route_command.go index b1fdb2b6880..ab232395dc8 100644 --- a/command/v7/share_route_command.go +++ b/command/v7/share_route_command.go @@ -11,7 +11,7 @@ type ShareRouteCommand struct { RequireArgs flag.Domain `positional-args:"yes"` Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` - DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` + DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` // ToDo: fix me DestinationSpace string `short:"s" description:"The space of the destination app (Default: targeted space)"` relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` diff --git a/command/v7/unshare_route_command.go b/command/v7/unshare_route_command.go new file mode 100644 index 00000000000..09a12d8b990 --- /dev/null +++ b/command/v7/unshare_route_command.go @@ -0,0 +1,101 @@ +package v7 + +import ( + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/command/flag" +) + +type UnshareRouteCommand struct { + BaseCommand + + RequireArgs flag.Domain `positional-args:"yes"` + Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` + Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` + DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` + DestinationSpace string `short:"s" description:"The space of the destination app (Default: targeted space)"` + + relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` +} + +func (cmd UnshareRouteCommand) Usage() string { + return ` +Share an existing route in between two spaces: + CF_NAME share-route DOMAIN [--hostname HOSTNAME] [--path PATH] -s OTHER_SPACE [-o OTHER_ORG]` +} + +func (cmd UnshareRouteCommand) Examples() string { + return ` +CF_NAME share-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo +CF_NAME share-route example.com --hostname myHost -s TargetSpace # myhost.example.com +CF_NAME share-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com` +} + +func (cmd UnshareRouteCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + domain, warnings, err := cmd.Actor.GetDomainByName(cmd.RequireArgs.Domain) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + path := cmd.Path.Path + route, warnings, err := cmd.Actor.GetRouteByAttributes(domain, cmd.Hostname, path, 0) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + if _, ok := err.(actionerror.RouteNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + destinationOrgName := cmd.DestinationOrg + + if destinationOrgName == "" { + destinationOrgName = cmd.Config.TargetedOrganizationName() + } + + destinationOrg, warnings, err := cmd.Actor.GetOrganizationByName(destinationOrgName) + + if err != nil { + if _, ok := err.(actionerror.OrganizationNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + targetedSpace, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.DestinationSpace, destinationOrg.GUID) + if err != nil { + if _, ok := err.(actionerror.SpaceNotFoundError); ok { + cmd.UI.DisplayText("Can not share route:") + return err + } + } + + url := desiredURL(domain.Name, cmd.Hostname, path, 0) + cmd.UI.DisplayTextWithFlavor("Unsharing route {{.URL}} from space {{.DestinationSpace}} as {{.User}}", + map[string]interface{}{ + "URL": url, + "DestinationSpace": cmd.DestinationSpace, + "User": user.Name, + }) + warnings, err = cmd.Actor.UnshareRoute( + route.GUID, + targetedSpace.GUID, + ) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + cmd.UI.DisplayOK() + + return nil +} diff --git a/command/v7/unshare_route_command_test.go b/command/v7/unshare_route_command_test.go new file mode 100644 index 00000000000..b3237815755 --- /dev/null +++ b/command/v7/unshare_route_command_test.go @@ -0,0 +1,280 @@ +package v7_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/actor/actionerror" + "code.cloudfoundry.org/cli/actor/v7action" + "code.cloudfoundry.org/cli/command/commandfakes" + "code.cloudfoundry.org/cli/command/flag" + v7 "code.cloudfoundry.org/cli/command/v7" + "code.cloudfoundry.org/cli/command/v7/v7fakes" + "code.cloudfoundry.org/cli/resources" + "code.cloudfoundry.org/cli/util/configv3" + "code.cloudfoundry.org/cli/util/ui" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("unshare-route Command", func() { + var ( + cmd v7.UnshareRouteCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + binaryName string + executeErr error + domainName string + orgName string + spaceName string + hostname string + path string + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + binaryName = "myBinaryBread" + fakeConfig.BinaryNameReturns(binaryName) + + domainName = "some-domain.com" + orgName = "org-name-a" + spaceName = "space-name-a" + hostname = "myHostname" + path = "myPath" + + cmd = v7.UnshareRouteCommand{ + BaseCommand: v7.BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + RequireArgs: flag.Domain{Domain: domainName}, + Hostname: hostname, + Path: flag.V7RoutePath{Path: path}, + DestinationOrg: orgName, + DestinationSpace: spaceName, + } + + fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space", GUID: "some-space-guid"}) + fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org"}) + fakeConfig.CurrentUserReturns(configv3.User{Name: "some-user"}, nil) + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + It("checks that target", func() { + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + + When("checking target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) + }) + It("returns an error", func() { + Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) + + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("the user is not logged in", func() { + var expectedErr error + + BeforeEach(func() { + expectedErr = errors.New("some current user error") + fakeActor.GetCurrentUserReturns(configv3.User{}, expectedErr) + }) + + It("return an error", func() { + Expect(executeErr).To(Equal(expectedErr)) + }) + }) + + When("the user is logged in and targeted", func() { + When("getting the domain errors", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns(resources.Domain{}, v7action.Warnings{"get-domain-warnings"}, errors.New("get-domain-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(executeErr).To(MatchError(errors.New("get-domain-error"))) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(0)) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(0)) + + Expect(fakeActor.UnshareRouteCallCount()).To(Equal(0)) + }) + }) + + When("getting the domain succeeds", func() { + BeforeEach(func() { + fakeActor.GetDomainByNameReturns( + resources.Domain{Name: domainName, GUID: "domain-guid"}, + v7action.Warnings{"get-domain-warnings"}, + nil, + ) + }) + + When("the requested route does not exist", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.RouteNotFoundError{}, + ) + }) + + It("displays error message", func() { + Expect(testUI.Err).To(Say("get-domain-warnings")) + Expect(testUI.Err).To(Say("get-route-warnings")) + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + }) + }) + + When("the requested route exists", func() { + BeforeEach(func() { + fakeActor.GetRouteByAttributesReturns( + resources.Route{GUID: "route-guid"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + When("getting the target space errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.SpaceNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetOrganizationByNameArgsForCall(0)).To(Equal(orgName)) + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + + Expect(fakeActor.UnshareRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target org errors", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{}, + v7action.Warnings{"get-route-warnings"}, + actionerror.OrganizationNotFoundError{}, + ) + }) + It("returns the error and warnings", func() { + Expect(executeErr).To(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.UnshareRouteCallCount()).To(Equal(0)) + }) + }) + When("getting the target space succeeds", func() { + BeforeEach(func() { + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-a"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "space-guid-b"}, + v7action.Warnings{"get-route-warnings"}, + nil, + ) + }) + It("exits 0 with helpful message that the route is now being unshared", func() { + Expect(executeErr).ShouldNot(HaveOccurred()) + + Expect(fakeActor.GetDomainByNameCallCount()).To(Equal(1)) + Expect(fakeActor.GetDomainByNameArgsForCall(0)).To(Equal(domainName)) + + Expect(fakeActor.GetRouteByAttributesCallCount()).To(Equal(1)) + actualDomain, actualHostname, actualPath, actualPort := fakeActor.GetRouteByAttributesArgsForCall(0) + Expect(actualDomain.Name).To(Equal(domainName)) + Expect(actualDomain.GUID).To(Equal("domain-guid")) + Expect(actualHostname).To(Equal(hostname)) + Expect(actualPath).To(Equal(path)) + Expect(actualPort).To(Equal(0)) + + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("org-name-a")) + + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGuid := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("space-name-a")) + Expect(orgGuid).To(Equal("org-guid-a")) + Expect(fakeActor.UnshareRouteCallCount()).To(Equal(1)) + }) + }) + }) + }) + }) +}) diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index f3cddabd4f7..f6684c33183 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -3148,6 +3148,20 @@ type FakeActor struct { result1 v7action.Warnings result2 error } + UnshareRouteStub func(string, string) (v7action.Warnings, error) + unshareRouteMutex sync.RWMutex + unshareRouteArgsForCall []struct { + arg1 string + arg2 string + } + unshareRouteReturns struct { + result1 v7action.Warnings + result2 error + } + unshareRouteReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } UnshareServiceInstanceFromSpaceAndOrgStub func(string, string, string, v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) unshareServiceInstanceFromSpaceAndOrgMutex sync.RWMutex unshareServiceInstanceFromSpaceAndOrgArgsForCall []struct { @@ -17115,6 +17129,70 @@ func (fake *FakeActor) UnsharePrivateDomainReturnsOnCall(i int, result1 v7action }{result1, result2} } +func (fake *FakeActor) UnshareRoute(arg1 string, arg2 string) (v7action.Warnings, error) { + fake.unshareRouteMutex.Lock() + ret, specificReturn := fake.unshareRouteReturnsOnCall[len(fake.unshareRouteArgsForCall)] + fake.unshareRouteArgsForCall = append(fake.unshareRouteArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + fake.recordInvocation("UnshareRoute", []interface{}{arg1, arg2}) + fake.unshareRouteMutex.Unlock() + if fake.UnshareRouteStub != nil { + return fake.UnshareRouteStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.unshareRouteReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) UnshareRouteCallCount() int { + fake.unshareRouteMutex.RLock() + defer fake.unshareRouteMutex.RUnlock() + return len(fake.unshareRouteArgsForCall) +} + +func (fake *FakeActor) UnshareRouteCalls(stub func(string, string) (v7action.Warnings, error)) { + fake.unshareRouteMutex.Lock() + defer fake.unshareRouteMutex.Unlock() + fake.UnshareRouteStub = stub +} + +func (fake *FakeActor) UnshareRouteArgsForCall(i int) (string, string) { + fake.unshareRouteMutex.RLock() + defer fake.unshareRouteMutex.RUnlock() + argsForCall := fake.unshareRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeActor) UnshareRouteReturns(result1 v7action.Warnings, result2 error) { + fake.unshareRouteMutex.Lock() + defer fake.unshareRouteMutex.Unlock() + fake.UnshareRouteStub = nil + fake.unshareRouteReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) UnshareRouteReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.unshareRouteMutex.Lock() + defer fake.unshareRouteMutex.Unlock() + fake.UnshareRouteStub = nil + if fake.unshareRouteReturnsOnCall == nil { + fake.unshareRouteReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.unshareRouteReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) UnshareServiceInstanceFromSpaceAndOrg(arg1 string, arg2 string, arg3 string, arg4 v7action.ServiceInstanceSharingParams) (v7action.Warnings, error) { fake.unshareServiceInstanceFromSpaceAndOrgMutex.Lock() ret, specificReturn := fake.unshareServiceInstanceFromSpaceAndOrgReturnsOnCall[len(fake.unshareServiceInstanceFromSpaceAndOrgArgsForCall)] @@ -19494,6 +19572,8 @@ func (fake *FakeActor) Invocations() map[string][][]interface{} { defer fake.unsetSpaceQuotaMutex.RUnlock() fake.unsharePrivateDomainMutex.RLock() defer fake.unsharePrivateDomainMutex.RUnlock() + fake.unshareRouteMutex.RLock() + defer fake.unshareRouteMutex.RUnlock() fake.unshareServiceInstanceFromSpaceAndOrgMutex.RLock() defer fake.unshareServiceInstanceFromSpaceAndOrgMutex.RUnlock() fake.updateAppFeatureMutex.RLock() diff --git a/integration/v7/isolated/unshare_route_command_test.go b/integration/v7/isolated/unshare_route_command_test.go new file mode 100644 index 00000000000..1ee9c751e95 --- /dev/null +++ b/integration/v7/isolated/unshare_route_command_test.go @@ -0,0 +1,208 @@ +package isolated + +import ( + "fmt" + + "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" + . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + . "github.com/onsi/gomega/gexec" +) + +var _ = FDescribe("unshare route command", func() { + Context("Help", func() { + It("appears in cf help -a", func() { + session := helpers.CF("help", "-a") + + fmt.Println(session) + + Eventually(session).Should(Exit(0)) + Expect(session).To(HaveCommandInCategoryWithDescription("unshare-route", "ROUTES", "Unshare a route in between spaces")) + }) + + It("displays the help information", func() { + session := helpers.CF("unshare-route", "--help") + Eventually(session).Should(Say(`NAME:`)) + Eventually(session).Should(Say("unshare-route - Unshare an existing route from a space")) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`USAGE:`)) + Eventually(session).Should(Say(`Unshare an existing route from a spaces:`)) + Eventually(session).Should(Say(`cf unshare-route DOMAIN \[--hostname HOSTNAME\] \[--path PATH\] -s OTHER_SPACE \[-o OTHER_ORG\]`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`EXAMPLES:`)) + Eventually(session).Should(Say(`cf unshare-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo`)) + Eventually(session).Should(Say(`cf unshare-route example.com --hostname myHost -s TargetSpace # myhost.example.com`)) + Eventually(session).Should(Say(`cf unshare-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`OPTIONS:`)) + Eventually(session).Should(Say(`--hostname, -n\s+Hostname for the HTTP route \(required for shared domains\)`)) + Eventually(session).Should(Say(`--path\s+Path for the HTTP route`)) + Eventually(session).Should(Say(`-o\s+The org of the destination app \(Default: targeted org\)`)) + Eventually(session).Should(Say(`-s\s+The space of the destination app \(Default: targeted space\)`)) + Eventually(session).Should(Say(`\n`)) + + Eventually(session).Should(Say(`SEE ALSO:`)) + Eventually(session).Should(Say(`create-route, map-route, routes, unmap-route`)) + + Eventually(session).Should(Exit(0)) + }) + }) + + When("the environment is not setup correctly", func() { + It("fails with the appropriate errors", func() { + helpers.CheckEnvironmentTargetedCorrectly(true, false, ReadOnlyOrg, "unshare-route", "some-domain", "-s SOME_SPACE") + }) + }) + + When("the environment is set up correctly", func() { + var ( + userName string + orgName string + spaceName string + ) + + BeforeEach(func() { + helpers.SkipIfVersionLessThan(ccversion.MinVersionHTTP2RoutingV3) + orgName = helpers.NewOrgName() + spaceName = helpers.NewSpaceName() + + helpers.SetupCF(orgName, spaceName) + userName, _ = helpers.GetCredentials() + }) + + AfterEach(func() { + helpers.QuickDeleteOrg(orgName) + }) + + When("the domain extists", func() { + var ( + domainName string + targetSpaceName string + ) + + BeforeEach(func() { + domainName = helpers.NewDomainName() + }) + + When("the route exists", func() { + var ( + domain helpers.Domain + hostname string + ) + When("the target space exists in targeted org", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("unshares the route to the destination space", func() { + session := helpers.CF("unshare-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say(`Unsharing route %s.%s from space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the target organization does not exist", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + targetOrgName = helpers.NewOrgName() + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 and an error message", func() { + session := helpers.CF("unshare-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not unshare route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + + When("the target space exists in another existing org", func() { + var targetOrgName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + targetOrgName = helpers.NewOrgName() + targetSpaceName = helpers.NewSpaceName() + helpers.CreateOrgAndSpace(targetOrgName, targetSpaceName) + helpers.SetupCF(orgName, spaceName) + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + AfterEach(func() { + domain.Delete() + }) + + It("unshared the route from the intended space", func() { + session := helpers.CF("unshare-route", domainName, "--hostname", hostname, "-o", targetOrgName, "-s", targetSpaceName) + Eventually(session).Should(Say(`Unsharing route %s.%s from space %s as %s`, hostname, domainName, targetSpaceName, userName)) + Eventually(session).Should(Say(`OK`)) + Eventually(session).Should(Exit(0)) + }) + }) + + When("the space does not exist", func() { + var destinationSpaceName string + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "menchies-icecream" + destinationSpaceName = "doesNotExistSpace" + domain.Create() + Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + }) + + It("exists with 1 with an error", func() { + session := helpers.CF("unshare-route", domainName, "--hostname", hostname, "-s", destinationSpaceName) + Eventually(session).Should(Say("Can not unshare route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + + When("the route does not exist", func() { + var ( + domain helpers.Domain + hostname string + ) + + When("the target space exists", func() { + BeforeEach(func() { + domain = helpers.NewDomain(orgName, domainName) + hostname = "panera-bread" + targetSpaceName = helpers.NewSpaceName() + helpers.CreateSpace(targetSpaceName) + domain.Create() + }) + + It("exits with 1 with an error message", func() { + session := helpers.CF("unshare-route", domainName, "--hostname", hostname, "-s", targetSpaceName) + Eventually(session).Should(Say("Can not unshare route:")) + Eventually(session).Should(Say(`FAILED`)) + Eventually(session).Should(Exit(1)) + }) + }) + }) + }) + }) +}) From bad75aedc2783fc33cc887292ad16dfeb46cbe96 Mon Sep 17 00:00:00 2001 From: Merric de Launey Date: Tue, 11 Oct 2022 21:35:41 +0000 Subject: [PATCH 035/543] Clean up help text in unshare route Authored-by: Merric de Launey --- command/common/command_list_v7.go | 2 +- command/v7/unshare_route_command.go | 22 +++++++++---------- .../v7/isolated/unshare_route_command_test.go | 15 +++++++------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index f0a1de1c307..2e1bc07ea5e 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -187,7 +187,7 @@ type commandList struct { UnsetSpaceQuota v7.UnsetSpaceQuotaCommand `command:"unset-space-quota" description:"Unassign a quota from a space"` UnsetSpaceRole v7.UnsetSpaceRoleCommand `command:"unset-space-role" description:"Remove a space role from a user"` UnsharePrivateDomain v7.UnsharePrivateDomainCommand `command:"unshare-private-domain" description:"Unshare a private domain with a specific org"` - UnshareRoute v7.UnshareRouteCommand `command:"unshare-route" description:"Unshare a shared route from a space"` + UnshareRoute v7.UnshareRouteCommand `command:"unshare-route" description:"Unshare an existing route from a space"` UnshareService v7.UnshareServiceCommand `command:"unshare-service" description:"Unshare a shared service instance from a space"` UpdateBuildpack v7.UpdateBuildpackCommand `command:"update-buildpack" description:"Update a buildpack"` UpdateDestination v7.UpdateDestinationCommand `command:"update-destination" description:"Updates the destination protocol for a route"` diff --git a/command/v7/unshare_route_command.go b/command/v7/unshare_route_command.go index 09a12d8b990..c25934b9746 100644 --- a/command/v7/unshare_route_command.go +++ b/command/v7/unshare_route_command.go @@ -11,23 +11,23 @@ type UnshareRouteCommand struct { RequireArgs flag.Domain `positional-args:"yes"` Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` - DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` - DestinationSpace string `short:"s" description:"The space of the destination app (Default: targeted space)"` + DestinationOrg string `short:"o" description:"The org of the destination space (Default: targeted org)"` + DestinationSpace string `short:"s" description:"The space to be unshared (Default: targeted space)"` - relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` + relatedCommands interface{} `related_commands:" share-route, delete-route, map-route, unmap-route, routes"` } func (cmd UnshareRouteCommand) Usage() string { return ` -Share an existing route in between two spaces: - CF_NAME share-route DOMAIN [--hostname HOSTNAME] [--path PATH] -s OTHER_SPACE [-o OTHER_ORG]` +Unshare an existing route from a space: + CF_NAME unshare-route DOMAIN [--hostname HOSTNAME] [--path PATH] -s OTHER_SPACE [-o OTHER_ORG]` } func (cmd UnshareRouteCommand) Examples() string { return ` -CF_NAME share-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo -CF_NAME share-route example.com --hostname myHost -s TargetSpace # myhost.example.com -CF_NAME share-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com` +CF_NAME unshare-route example.com --hostname myHost --path foo -s TargetSpace -o TargetOrg # myhost.example.com/foo +CF_NAME unshare-route example.com --hostname myHost -s TargetSpace # myhost.example.com +CF_NAME unshare-route example.com --hostname myHost -s TargetSpace -o TargetOrg # myhost.example.com` } func (cmd UnshareRouteCommand) Execute(args []string) error { @@ -52,7 +52,7 @@ func (cmd UnshareRouteCommand) Execute(args []string) error { cmd.UI.DisplayWarnings(warnings) if err != nil { if _, ok := err.(actionerror.RouteNotFoundError); ok { - cmd.UI.DisplayText("Can not share route:") + cmd.UI.DisplayText("Can not unshare route:") return err } } @@ -67,7 +67,7 @@ func (cmd UnshareRouteCommand) Execute(args []string) error { if err != nil { if _, ok := err.(actionerror.OrganizationNotFoundError); ok { - cmd.UI.DisplayText("Can not share route:") + cmd.UI.DisplayText("Can not unshare route:") return err } } @@ -75,7 +75,7 @@ func (cmd UnshareRouteCommand) Execute(args []string) error { targetedSpace, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.DestinationSpace, destinationOrg.GUID) if err != nil { if _, ok := err.(actionerror.SpaceNotFoundError); ok { - cmd.UI.DisplayText("Can not share route:") + cmd.UI.DisplayText("Can not unshare route:") return err } } diff --git a/integration/v7/isolated/unshare_route_command_test.go b/integration/v7/isolated/unshare_route_command_test.go index 1ee9c751e95..b58260bd0f4 100644 --- a/integration/v7/isolated/unshare_route_command_test.go +++ b/integration/v7/isolated/unshare_route_command_test.go @@ -12,7 +12,7 @@ import ( . "github.com/onsi/gomega/gexec" ) -var _ = FDescribe("unshare route command", func() { +var _ = Describe("unshare route command", func() { Context("Help", func() { It("appears in cf help -a", func() { session := helpers.CF("help", "-a") @@ -20,7 +20,7 @@ var _ = FDescribe("unshare route command", func() { fmt.Println(session) Eventually(session).Should(Exit(0)) - Expect(session).To(HaveCommandInCategoryWithDescription("unshare-route", "ROUTES", "Unshare a route in between spaces")) + Expect(session).To(HaveCommandInCategoryWithDescription("unshare-route", "ROUTES", "Unshare an existing route from a space")) }) It("displays the help information", func() { @@ -30,7 +30,7 @@ var _ = FDescribe("unshare route command", func() { Eventually(session).Should(Say(`\n`)) Eventually(session).Should(Say(`USAGE:`)) - Eventually(session).Should(Say(`Unshare an existing route from a spaces:`)) + Eventually(session).Should(Say(`Unshare an existing route from a space:`)) Eventually(session).Should(Say(`cf unshare-route DOMAIN \[--hostname HOSTNAME\] \[--path PATH\] -s OTHER_SPACE \[-o OTHER_ORG\]`)) Eventually(session).Should(Say(`\n`)) @@ -43,12 +43,12 @@ var _ = FDescribe("unshare route command", func() { Eventually(session).Should(Say(`OPTIONS:`)) Eventually(session).Should(Say(`--hostname, -n\s+Hostname for the HTTP route \(required for shared domains\)`)) Eventually(session).Should(Say(`--path\s+Path for the HTTP route`)) - Eventually(session).Should(Say(`-o\s+The org of the destination app \(Default: targeted org\)`)) - Eventually(session).Should(Say(`-s\s+The space of the destination app \(Default: targeted space\)`)) + Eventually(session).Should(Say(`-o\s+The org of the destination space \(Default: targeted org\)`)) + Eventually(session).Should(Say(`-s\s+The space to be unshared \(Default: targeted space\)`)) Eventually(session).Should(Say(`\n`)) Eventually(session).Should(Say(`SEE ALSO:`)) - Eventually(session).Should(Say(`create-route, map-route, routes, unmap-route`)) + Eventually(session).Should(Say(`share-route, delete-route, map-route, routes, unmap-route`)) Eventually(session).Should(Exit(0)) }) @@ -95,7 +95,7 @@ var _ = FDescribe("unshare route command", func() { domain helpers.Domain hostname string ) - When("the target space exists in targeted org", func() { + When("the target space is shared in the targedted space", func() { BeforeEach(func() { domain = helpers.NewDomain(orgName, domainName) hostname = "panera-bread" @@ -103,6 +103,7 @@ var _ = FDescribe("unshare route command", func() { helpers.CreateSpace(targetSpaceName) domain.Create() Eventually(helpers.CF("create-route", domain.Name, "--hostname", hostname)).Should(Exit(0)) + Eventually(helpers.CF("share-route", domain.Name, "--hostname", hostname, "-s", targetSpaceName)).Should(Exit(0)) }) AfterEach(func() { From 792ea7844126b2a5595c2411755b29bb0dc2682d Mon Sep 17 00:00:00 2001 From: Merric de Launey Date: Tue, 11 Oct 2022 22:06:33 +0000 Subject: [PATCH 036/543] clarify share-route description --- command/v7/share_route_command.go | 4 ++-- integration/v7/isolated/share_route_command_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/command/v7/share_route_command.go b/command/v7/share_route_command.go index ab232395dc8..1b645fc3a78 100644 --- a/command/v7/share_route_command.go +++ b/command/v7/share_route_command.go @@ -11,8 +11,8 @@ type ShareRouteCommand struct { RequireArgs flag.Domain `positional-args:"yes"` Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` - DestinationOrg string `short:"o" description:"The org of the destination app (Default: targeted org)"` // ToDo: fix me - DestinationSpace string `short:"s" description:"The space of the destination app (Default: targeted space)"` + DestinationOrg string `short:"o" description:"The org of the destination space (Default: targeted org)"` // ToDo: fix me + DestinationSpace string `short:"s" description:"The space the route will be shared with (Default: targeted space)"` relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` } diff --git a/integration/v7/isolated/share_route_command_test.go b/integration/v7/isolated/share_route_command_test.go index d3f8affe88b..68cbc538bcb 100644 --- a/integration/v7/isolated/share_route_command_test.go +++ b/integration/v7/isolated/share_route_command_test.go @@ -39,8 +39,8 @@ var _ = Describe("share route command", func() { Eventually(session).Should(Say(`OPTIONS:`)) Eventually(session).Should(Say(`--hostname, -n\s+Hostname for the HTTP route \(required for shared domains\)`)) Eventually(session).Should(Say(`--path\s+Path for the HTTP route`)) - Eventually(session).Should(Say(`-o\s+The org of the destination app \(Default: targeted org\)`)) - Eventually(session).Should(Say(`-s\s+The space of the destination app \(Default: targeted space\)`)) + Eventually(session).Should(Say(`-o\s+The org of the destination space \(Default: targeted org\)`)) + Eventually(session).Should(Say(`-s\s+The space the route will be shared with \(Default: targeted space\)`)) Eventually(session).Should(Say(`\n`)) Eventually(session).Should(Say(`SEE ALSO:`)) From 0a955e28c0524c2e9aac82d45ecb34fcb78b0718 Mon Sep 17 00:00:00 2001 From: Merric de Launey Date: Wed, 12 Oct 2022 16:58:51 +0000 Subject: [PATCH 037/543] remove todo --- command/v7/share_route_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/v7/share_route_command.go b/command/v7/share_route_command.go index 1b645fc3a78..8f36268da50 100644 --- a/command/v7/share_route_command.go +++ b/command/v7/share_route_command.go @@ -11,7 +11,7 @@ type ShareRouteCommand struct { RequireArgs flag.Domain `positional-args:"yes"` Hostname string `long:"hostname" short:"n" description:"Hostname for the HTTP route (required for shared domains)"` Path flag.V7RoutePath `long:"path" description:"Path for the HTTP route"` - DestinationOrg string `short:"o" description:"The org of the destination space (Default: targeted org)"` // ToDo: fix me + DestinationOrg string `short:"o" description:"The org of the destination space (Default: targeted org)"` DestinationSpace string `short:"s" description:"The space the route will be shared with (Default: targeted space)"` relatedCommands interface{} `related_commands:"create-route, map-route, unmap-route, routes"` From 03c2415dbac020c63525d4339acbf1b102b14bdc Mon Sep 17 00:00:00 2001 From: Ryker Reed Date: Thu, 13 Oct 2022 14:34:06 -0400 Subject: [PATCH 038/543] Fix references to cli-ci default branch to use main. --- .github/workflows/build-sign-upload.yml | 4 ++-- .github/workflows/update-repos.yml | 2 +- doc/installation-instructions/installation-instructions-v6.md | 2 +- doc/installation-instructions/installation-instructions-v7.md | 2 +- integration/README.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-sign-upload.yml b/.github/workflows/build-sign-upload.yml index 6089c77c323..8d66537d8ba 100644 --- a/.github/workflows/build-sign-upload.yml +++ b/.github/workflows/build-sign-upload.yml @@ -176,7 +176,7 @@ jobs: with: repository: cloudfoundry/cli-ci.git path: cli-ci - ref: master + ref: main - name: Install Linux Packages run: sudo apt update && sudo apt install -y --no-install-recommends fakeroot @@ -417,7 +417,7 @@ jobs: with: repository: cloudfoundry/cli-ci.git path: cli-ci - ref: master + ref: main - name: Checkout bomutils uses: actions/checkout@v2 diff --git a/.github/workflows/update-repos.yml b/.github/workflows/update-repos.yml index c12e52459a3..b665c095e85 100644 --- a/.github/workflows/update-repos.yml +++ b/.github/workflows/update-repos.yml @@ -52,7 +52,7 @@ jobs: uses: actions/checkout@v2 with: repository: cloudfoundry/cli-ci.git - ref: master + ref: main path: cli-ci - name: Checkout homebrew-tap diff --git a/doc/installation-instructions/installation-instructions-v6.md b/doc/installation-instructions/installation-instructions-v6.md index 3e995217756..bf9087a7e7b 100644 --- a/doc/installation-instructions/installation-instructions-v6.md +++ b/doc/installation-instructions/installation-instructions-v6.md @@ -48,7 +48,7 @@ curl -L "https://packages.cloudfoundry.org/stable?release=linux64-binary&source= # ...move it to /usr/local/bin or a location you know is in your $PATH mv cf /usr/local/bin # ...copy tab completion file on Ubuntu (takes affect after re-opening your shell) -sudo curl -o /usr/share/bash-completion/completions/cf https://raw.githubusercontent.com/cloudfoundry/cli-ci/master/ci/installers/completion/cf +sudo curl -o /usr/share/bash-completion/completions/cf https://raw.githubusercontent.com/cloudfoundry/cli-ci/main/ci/installers/completion/cf # ...and to confirm your cf CLI version cf version ``` diff --git a/doc/installation-instructions/installation-instructions-v7.md b/doc/installation-instructions/installation-instructions-v7.md index ac4489883ae..99d980abe7b 100644 --- a/doc/installation-instructions/installation-instructions-v7.md +++ b/doc/installation-instructions/installation-instructions-v7.md @@ -55,7 +55,7 @@ curl -L "https://packages.cloudfoundry.org/stable?release=linux64-binary&version # ...move it to /usr/local/bin or a location you know is in your $PATH mv cf /usr/local/bin # ...copy tab completion file on Ubuntu (takes affect after re-opening your shell) -sudo curl -o /usr/share/bash-completion/completions/cf7 https://raw.githubusercontent.com/cloudfoundry/cli-ci/master/ci/installers/completion/cf7 +sudo curl -o /usr/share/bash-completion/completions/cf7 https://raw.githubusercontent.com/cloudfoundry/cli-ci/main/ci/installers/completion/cf7 # ...and to confirm your cf CLI version cf version ``` diff --git a/integration/README.md b/integration/README.md index de326244cb7..564021f8dad 100644 --- a/integration/README.md +++ b/integration/README.md @@ -61,7 +61,7 @@ ginkgo -r -randomizeAllSpecs -slowSpecThreshold=120 integration/shared/global in - `KEEP_FAKE_SERVICE_BROKERS` - If `true`, will not delete any deployed reusable fake service broker apps. Useful for local development: allows for faster test suite re-runs. Will default to `false` if not set. ### The test suite does not cleanup after itself! -In order to focus on clean test code and performance of each test, we have decided to not cleanup after each test. However, in order to facilitate [clean up scripts](https://github.com/cloudfoundry/cli-ci/blob/master/bin/cleanup-integration), we are trying to keep consistent naming across organizations, spaces, etc. +In order to focus on clean test code and performance of each test, we have decided to not cleanup after each test. However, in order to facilitate [clean up scripts](https://github.com/cloudfoundry/cli-ci/blob/main/bin/cleanup-integration), we are trying to keep consistent naming across organizations, spaces, etc. In addition, several router groups are created using a `INTEGRATION-TCP-NODE-[NUMBER]` format. These cannot be deleted without manual changes to the database. From 23a1033fb32a8bc5bf1525d5f6279f896f71d6bc Mon Sep 17 00:00:00 2001 From: Al Berez Date: Tue, 18 Oct 2022 11:12:43 -0700 Subject: [PATCH 039/543] GitHub Actions Integration Tests (#2326) * Add GitHub Actions for Linux Integration Test with latest CAPI - this leaves some room for future reuse (like min capi and windows) - not sure we should use upload artifact (maybe we can save just the enviornment name from toolsmiths and call down the metafile when needed?) - install tools when needed rather than use cf-deployment-concourse-tasks container * You apparently can't reference things outside of workflows for uses - so moved the directory * Use environment name as identifier for metadata instead of hardcoding - also fix a bug with wrong variable name in unclaim - adding Shwetha cause all the work has been cribbed off of her initial start Co-authored-by: Shwetha Gururaj * Apparently you can't have any nested folders with workflows in github actions * inline url cause actions not happy with extraction * Separate tools installation from claim environment Co-authored-by: Michael Oleske Co-authored-by: Al Berez * Remove unneeded && \ * Intelligent toolsmith error handling Optimize steps order Co-authored-by: Michael Oleske * Make TOOLSMITHS_HOSTNAME secret Co-authored-by: Michael Oleske * Use correct path for aws install - use absolute path (`/tmp/aws/install`) instead of relative path (`./tmp/aws/install`) * Use output of step instead of job for setup-cf-env - previously was trying to get the output of a job, but the output is actually from a step, so would be blank * Add step output to setup cf env - you need to also output on the step, which you then pass to the job output * remove aws cli install aws tools already exist on [github provided images](https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md#cli-tools) * Use /usr/local/bin for bosh also add print of bosh --version and bbl --version just to make sure they are available * use /usr/local/bin for bosh missed this in the last commit :( * extract credhub to tmp and then move to /usr/local/bin resolve the following error cause by tar permission issues ``` tar: .: Cannot utime: Operation not permitted tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted tar: Exiting with failure status due to previous errors ``` * write credhub.tgz to home directory github provided machines don't allow tar to create folders in tmp for some reason, so this really resolves the error in the previously mentioned commit * Delets unecessary line Unclaiming the toolsmith environment does not need the integration test to be completed with an output in order to run the unclaim job * don't need to cd into cli as that's what checkout does - use quotes on some exports when getting passwords out of credhub * Add built cli to path - because `make integration-tests-full-ci` expects cf cli to be on the path * remove oidc credentials cf toolsmiths environments don't have an oidc provider configured * force unclaim to only run after run-integration-tests - we removed this before, but it is needed to make sure we don't run the unclaim job before the integration tests run. Otherwise we've unclaimed and destroyed the environment before we even run integration tests! Co-authored-by: M. Oleske Co-authored-by: Shwetha Gururaj Co-authored-by: M. Oleske Co-authored-by: Juan Diego --- .github/workflows/integration.yml | 122 +++++++++++++++++++++++++++ .github/workflows/setup-cf-env.yml | 95 +++++++++++++++++++++ .github/workflows/unclaim-cf-env.yml | 43 ++++++++++ 3 files changed, 260 insertions(+) create mode 100644 .github/workflows/integration.yml create mode 100644 .github/workflows/setup-cf-env.yml create mode 100644 .github/workflows/unclaim-cf-env.yml diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 00000000000..75be0376a87 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,122 @@ +name: Integration Tests + +on: + workflow_dispatch: + pull_request: + branches: + - master + - v8 + - v7 + paths-ignore: + - 'doc/**' + - '.github/**' + - '.gitpod.yml' + - 'README.md' + push: + branches: + - master + - v8 + - v7 + paths-ignore: + - 'doc/**' + - '.github/**' + - '.gitpod.yml' + - 'README.md' + +permissions: + contents: read + +jobs: + shared-values: + name: Shared Values + runs-on: ubuntu-latest + outputs: + secrets-environment: ${{ steps.set-secrets-environment.outputs.secrets-environment }} + go-version: ${{ steps.set-go-version.outputs.go-version }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - id: set-secrets-environment + name: Set environment + run: echo "::set-output name=secrets-environment::PROD" + + - id: set-go-version + name: Parse Golang Version + run: | + go_version=($(grep -E '^go 1\.[[:digit:]]{1,2}' go.mod)) + echo "golang version: ${go_version[1]}" + echo "::set-output name=go-version::${go_version[1]}" + + get-linux-env-with-edge-capi: + needs: shared-values + uses: ./.github/workflows/setup-cf-env.yml + with: + environment: ${{ needs.shared-values.outputs.secrets-environment }} + capi-version: edge + secrets: inherit + + run-integration-tests-linux-env-with-edge-capi: + needs: + - shared-values + - get-linux-env-with-edge-capi + runs-on: ubuntu-latest + environment: ${{ needs.shared-values.outputs.secrets-environment }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set Up Go + uses: actions/setup-go@v3 + with: + go-version: ${{ needs.shared-values.outputs.go-version}} + check-latest: true + - name: Download metadata + uses: actions/download-artifact@v3 + with: + name: ${{ needs.get-linux-env-with-edge-capi.outputs.environment-name }} + - name: Install Tools + run: | + wget https://github.com/cloudfoundry/bosh-bootloader/releases/download/v8.4.110/bbl-v8.4.110_linux_x86-64 -P /tmp + mv /tmp/bbl-* /usr/local/bin/bbl + chmod +x /usr/local/bin/bbl + bbl --version + + wget https://s3.amazonaws.com/bosh-cli-artifacts/bosh-cli-7.0.1-linux-amd64 --output-document="/usr/local/bin/bosh" + chmod +x /usr/local/bin/bosh + bosh --version + + wget https://github.com/cloudfoundry/credhub-cli/releases/download/2.9.4/credhub-linux-2.9.4.tgz -P ~/ + tar xzvf ~/credhub-linux-2.9.4.tgz + mv credhub /usr/local/bin/credhub + chmod +x /usr/local/bin/credhub + credhub --version + rm ~/credhub-linux-2.9.4.tgz + - name: Run Integration Tests + run: | + ENV=$(cat metadata.json | jq -r '.name') + eval "$(bbl print-env --metadata-file ./metadata.json)" + export CF_INT_PASSWORD="$(credhub get -n /bosh-$ENV/cf/cf_admin_password | bosh interpolate --path /value -)" + export CF_INT_API="https://api.${ENV}.cf-app.com" + export CF_DIAL_TIMEOUT=15 + export CF_USERNAME=admin + export FLAKE_ATTEMPTS=2 + export NODES=16 + go install github.com/onsi/ginkgo/ginkgo@v1.16.4 + + make build + export PATH="$(pwd)/out:$PATH" + make integration-tests-full-ci + + unclaim-linux-env-with-edge-capi: + needs: + - shared-values + - get-linux-env-with-edge-capi + - run-integration-tests-linux-env-with-edge-capi + if: always() + uses: ./.github/workflows/unclaim-cf-env.yml + with: + environment: ${{ needs.shared-values.outputs.secrets-environment }} + identifier-for-metadata-file: ${{ needs.get-linux-env-with-edge-capi.outputs.environment-name }} + secrets: inherit diff --git a/.github/workflows/setup-cf-env.yml b/.github/workflows/setup-cf-env.yml new file mode 100644 index 00000000000..0e6a813c037 --- /dev/null +++ b/.github/workflows/setup-cf-env.yml @@ -0,0 +1,95 @@ +name: Setup CF Environment + +on: + workflow_call: + inputs: + environment: + required: true + type: string + capi-version: + required: true + type: string + outputs: + environment-name: + description: "Name of claimed environment" + value: ${{ jobs.setup-cf-env.outputs.environment-name }} + +jobs: + setup-cf-env: + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + outputs: + environment-name: ${{ steps.claim-toolsmiths-env.outputs.environment-name }} + steps: + - id: claim-toolsmiths-env + name: Claim Toolsmiths Environment + env: + api_token: ${{ secrets.TOOLSMITHS_API_TOKEN }} + hostname: ${{ secrets.TOOLSMITHS_HOSTNAME }} + notes: CF CLI Github Actions Integration Tests + pool_name: cf-deployment + run: | + while true; do + curl -s --show-error -D >(tee headers.txt >&2) -H 'Accept: application/json' \ + -X POST "https://${hostname}/pooled_gcp_engineering_environments/claim" \ + --data-urlencode "api_token=${api_token}" \ + --data-urlencode "pool_name=${pool_name}" \ + --data-urlencode "notes=${notes}" > metadata.json \ + || echo "Unable to reach server, trying again in 30 seconds..." + + ERR_500="Sorry, the Toolsmiths Environments app is currently encountering issues. Trying again in 30 seconds..." + ERR_429="Sorry, Toolsmiths are out of environments in your requested pool. New environments are on their way but you can stop by the Toolsmiths slack channel for more help." + ERR_409="Sorry, was not able to claim an environment. Trying again in 30 seconds..." + + grep -q -E "HTTP/[[:digit:]\.]{1,3} 401" headers.txt && exit 1 + grep -q -E "HTTP/[[:digit:]\.]{1,3} 404" headers.txt && exit 2 + grep -q -E "HTTP/[[:digit:]\.]{1,3} 500" headers.txt && echo "$ERR_500" + grep -q -E "HTTP/[[:digit:]\.]{1,3} 200" headers.txt && break + grep -q -E "HTTP/[[:digit:]\.]{1,3} 429" && echo "$ERR_429" + grep -q -E "HTTP/[[:digit:]\.]{1,3} 409" && echo "$ERR_409" + + sleep 30 + done + + ENV=$(cat metadata.json | jq -r '.name') + echo "::set-output name=environment-name::${ENV}" + + - name: 'Upload Metadata' + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.claim-toolsmiths-env.outputs.environment-name }} + path: metadata.json + + - name: Checkout cli-ci + uses: actions/checkout@v3 + with: + repository: cloudfoundry/cli-ci + path: cli-ci + + - name: Install Tools + run: | + wget https://github.com/cloudfoundry/bosh-bootloader/releases/download/v8.4.110/bbl-v8.4.110_linux_x86-64 -P /tmp + mv /tmp/bbl-* /usr/local/bin/bbl + chmod +x /usr/local/bin/bbl + bbl --version + + wget https://s3.amazonaws.com/bosh-cli-artifacts/bosh-cli-7.0.1-linux-amd64 --output-document="/usr/local/bin/bosh" + chmod +x /usr/local/bin/bosh + bosh --version + + - name: Deploy edge CAPI + if: ${{ inputs.capi-version == 'edge' }} + run: | + # find latest capi + FILENAME="$(aws s3 ls capi-releases --no-sign-request --recursive --region us-east-1 | sort | tail -n 1 | awk '{print $4}')" + aws s3 cp s3://capi-releases/$FILENAME $FILENAME --no-sign-request --region us-east-1 + eval "$(bbl print-env --metadata-file metadata.json)" + bosh upload-release --sha2 "$FILENAME" + rm $FILENAME + + # deploy + bosh -d cf manifest > /tmp/manifest.json + bosh -d cf deploy /tmp/manifest.json -o cli-ci/ci/infrastructure/operations/use-latest-capi.yml -n + + echo "Deployed CAPI version:" + bosh -d cf releases | grep capi diff --git a/.github/workflows/unclaim-cf-env.yml b/.github/workflows/unclaim-cf-env.yml new file mode 100644 index 00000000000..6871545663f --- /dev/null +++ b/.github/workflows/unclaim-cf-env.yml @@ -0,0 +1,43 @@ +name: Unclaim an environment + +on: + workflow_call: + inputs: + environment: + required: true + type: string + identifier-for-metadata-file: + required: true + type: string + +jobs: + build-env: + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + + steps: + - name: Download metadata + uses: actions/download-artifact@v3 + with: + name: ${{ inputs.identifier-for-metadata-file }} + + - name: Unclaim environment + env: + api_token: ${{ secrets.TOOLSMITHS_API_TOKEN }} + hostname: ${{ secrets.TOOLSMITHS_HOSTNAME }} + run: | + env_name=$(cat metadata.json | jq -r .name) + + while true; do + output=$(curl -s --show-error -D >(tee headers.txt >&2) -H 'Accept: application/json' \ + -X POST "https://${hostname}/pooled_gcp_engineering_environments/unclaim" \ + --data-urlencode "api_token=${api_token}" \ + --data-urlencode "name=${env_name}") + + ERR_500="Sorry, the Toolsmiths Environments app is currently encountering issues. Trying again in 30 seconds..." + + grep -q -E "HTTP/[[:digit:]\.]{1,3} 500" headers.txt && echo "$ERR_500" && sleep 30 && continue + grep -q -E "HTTP/[[:digit:]\.]{1,3} 401" headers.txt && echo $(echo "$output" | jq '.messages | join(", ")') && exit 1 + grep -q -E "HTTP/[[:digit:]\.]{1,3} 404" headers.txt && echo $(echo "$output" | jq '.messages | join(", ")') && exit 2 + grep -q -E "HTTP/[[:digit:]\.]{1,3} 202" headers.txt && break + done \ No newline at end of file From 27ae058fcdb24db4730a4b1a962fac4631038a04 Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Thu, 20 Oct 2022 15:28:54 -0700 Subject: [PATCH 040/543] Deploys OIDC provider for github integration tests Co-authored-by: Michael Oleske Co-authored-by: Al Berez --- .github/workflows/integration.yml | 2 ++ .github/workflows/setup-cf-env.yml | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 75be0376a87..5c5b61854e4 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -98,6 +98,8 @@ jobs: ENV=$(cat metadata.json | jq -r '.name') eval "$(bbl print-env --metadata-file ./metadata.json)" export CF_INT_PASSWORD="$(credhub get -n /bosh-$ENV/cf/cf_admin_password | bosh interpolate --path /value -)" + export CF_INT_OIDC_USERNAME="admin-oidc" + export CF_INT_OIDC_PASSWORD=$(credhub get -n /bosh-$ENV/cf/uaa_oidc_admin_password | bosh interpolate --path /value -) export CF_INT_API="https://api.${ENV}.cf-app.com" export CF_DIAL_TIMEOUT=15 export CF_USERNAME=admin diff --git a/.github/workflows/setup-cf-env.yml b/.github/workflows/setup-cf-env.yml index 0e6a813c037..d6250e75505 100644 --- a/.github/workflows/setup-cf-env.yml +++ b/.github/workflows/setup-cf-env.yml @@ -77,7 +77,7 @@ jobs: chmod +x /usr/local/bin/bosh bosh --version - - name: Deploy edge CAPI + - name: Deploy edge CAPI and OIDC Provider if: ${{ inputs.capi-version == 'edge' }} run: | # find latest capi @@ -89,7 +89,9 @@ jobs: # deploy bosh -d cf manifest > /tmp/manifest.json - bosh -d cf deploy /tmp/manifest.json -o cli-ci/ci/infrastructure/operations/use-latest-capi.yml -n + bosh -n -d cf deploy /tmp/manifest.json \ + -o cli-ci/ci/infrastructure/operations/use-latest-capi.yml \ + -o cli-ci/ci/infrastructure/operations/add-oidc-provider.yml echo "Deployed CAPI version:" bosh -d cf releases | grep capi From 4f8c617873d845f67f4ce73571fcd95066f8527c Mon Sep 17 00:00:00 2001 From: Al Berez Date: Thu, 20 Oct 2022 10:47:38 -0700 Subject: [PATCH 041/543] Trigger integration tests on PRs for changes in .github folder Since we can't manually trigger GHA on remote branch from PR. Co-authored-by: Michael Oleske --- .github/workflows/integration.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 5c5b61854e4..9817595250a 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -9,7 +9,6 @@ on: - v7 paths-ignore: - 'doc/**' - - '.github/**' - '.gitpod.yml' - 'README.md' push: From 4b0fcc0b443efedd7a1b99615fc46fe60e3077eb Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Fri, 21 Oct 2022 17:39:47 -0400 Subject: [PATCH 042/543] Add Isolation Segment to GitHub Actions Integration Tests (#2335) * Add Isolation Segment * Allows tests that rely on an isolation segment being available to run * Trigger integration tests on PRs for changes in .github folder * Break up bosh deploy line into multiple smaller lines Co-authored-by: Michael Oleske Co-authored-by: Al Berez --- .github/workflows/setup-cf-env.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/setup-cf-env.yml b/.github/workflows/setup-cf-env.yml index d6250e75505..da936476638 100644 --- a/.github/workflows/setup-cf-env.yml +++ b/.github/workflows/setup-cf-env.yml @@ -66,6 +66,12 @@ jobs: repository: cloudfoundry/cli-ci path: cli-ci + - name: Checkout cf-deployment + uses: actions/checkout@v3 + with: + repository: cloudfoundry/cf-deployment + path: cf-deployment + - name: Install Tools run: | wget https://github.com/cloudfoundry/bosh-bootloader/releases/download/v8.4.110/bbl-v8.4.110_linux_x86-64 -P /tmp @@ -77,7 +83,7 @@ jobs: chmod +x /usr/local/bin/bosh bosh --version - - name: Deploy edge CAPI and OIDC Provider + - name: Deploy edge CAPI with Isolation Segment and OIDC Provider if: ${{ inputs.capi-version == 'edge' }} run: | # find latest capi @@ -91,6 +97,7 @@ jobs: bosh -d cf manifest > /tmp/manifest.json bosh -n -d cf deploy /tmp/manifest.json \ -o cli-ci/ci/infrastructure/operations/use-latest-capi.yml \ + -o cf-deployment/operations/test/add-persistent-isolation-segment-diego-cell.yml \ -o cli-ci/ci/infrastructure/operations/add-oidc-provider.yml echo "Deployed CAPI version:" From 711a3469face4fcc81ab3e9d4d3af6d293de4a1d Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Thu, 13 Oct 2022 15:36:46 -0700 Subject: [PATCH 043/543] Use default of 1GB memory instead of 32M - a regular deploy of cf-deployment has a default memory limit of 1G - if we prefer to use 32M, we should change the tests to push an app specifying that as the limit instead --- integration/v7/isolated/scale_command_test.go | 4 ++-- integration/v7/isolated/start_command_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/v7/isolated/scale_command_test.go b/integration/v7/isolated/scale_command_test.go index a256094ca43..7e2723e60c6 100644 --- a/integration/v7/isolated/scale_command_test.go +++ b/integration/v7/isolated/scale_command_test.go @@ -104,7 +104,7 @@ var _ = Describe("scale command", func() { helpers.WithProcfileApp(func(appDir string) { Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "push", appName)).Should(Exit(0)) }) - helpers.WaitForAppMemoryToTakeEffect(appName, 0, 0, false, "32M") + helpers.WaitForAppMemoryToTakeEffect(appName, 0, 0, false, "1G") }) When("scale option flags are not provided", func() { @@ -161,7 +161,7 @@ var _ = Describe("scale command", func() { Consistently(session).ShouldNot(Say("Stopping")) Consistently(session).ShouldNot(Say("Starting")) - helpers.WaitForAppMemoryToTakeEffect(appName, 0, 0, true, "32M") + helpers.WaitForAppMemoryToTakeEffect(appName, 0, 0, true, "1G") session = helpers.CF("app", appName) Eventually(session).Should(Exit(0)) diff --git a/integration/v7/isolated/start_command_test.go b/integration/v7/isolated/start_command_test.go index b1bbfac5a81..12f4f588e62 100644 --- a/integration/v7/isolated/start_command_test.go +++ b/integration/v7/isolated/start_command_test.go @@ -129,7 +129,7 @@ var _ = Describe("start command", func() { Eventually(session).Should(Say(`routes:\s+%s.%s`, appName, helpers.DefaultSharedDomain())) Eventually(session).Should(Say(`type:\s+web`)) Eventually(session).Should(Say(`instances:\s+1/1`)) - Eventually(session).Should(Say(`memory usage:\s+32M`)) + Eventually(session).Should(Say(`memory usage:\s+1024M`)) Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+logging\s+details`)) Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`)) From 426fde62cceb2afcc031be805b2d00d692632626 Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Fri, 21 Oct 2022 17:59:22 -0400 Subject: [PATCH 044/543] Explicity enable route_sharing feature on tests that need it (#2331) * Explicity enable route_sharing feature on tests that need it - previously tests were not stating they needed this feature enable, only a minimum version of capi - since the feature is not enabled by default, this adds a call to enable it * Enable route_sharing in common isolated test setup * Fix formatting Co-authored-by: Michael Oleske Co-authored-by: Alexander Berez --- .../commonisolated/common_isolated_setup.go | 123 +++++++++--------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/integration/helpers/commonisolated/common_isolated_setup.go b/integration/helpers/commonisolated/common_isolated_setup.go index 0960c1acf68..d20baf1cff8 100644 --- a/integration/helpers/commonisolated/common_isolated_setup.go +++ b/integration/helpers/commonisolated/common_isolated_setup.go @@ -1,86 +1,87 @@ package commonisolated import ( - "fmt" - "testing" - "time" + "fmt" + "testing" + "time" - "code.cloudfoundry.org/cli/integration/helpers" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "code.cloudfoundry.org/cli/integration/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) const ( - CFEventuallyTimeout = 300 * time.Second - CFConsistentlyTimeout = 500 * time.Millisecond - RealIsolationSegment = "persistent_isolation_segment" - DockerImage = "cloudfoundry/diego-docker-app-custom" + CFEventuallyTimeout = 300 * time.Second + CFConsistentlyTimeout = 500 * time.Millisecond + RealIsolationSegment = "persistent_isolation_segment" + DockerImage = "cloudfoundry/diego-docker-app-custom" ) func CommonTestIsolated(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Isolated Integration Suite") + RegisterFailHandler(Fail) + RunSpecs(t, "Isolated Integration Suite") } func CommonGinkgoSetup( - // Per suite Level - failureSummaryFilename string, - apiURL *string, - skipSSLValidation *bool, - readOnlyOrg *string, - readOnlySpace *string, + // Per suite Level + failureSummaryFilename string, + apiURL *string, + skipSSLValidation *bool, + readOnlyOrg *string, + readOnlySpace *string, - // Per test level - homeDir *string, + // Per test level + homeDir *string, ) interface{} { - var _ = SynchronizedBeforeSuite(func() []byte { - _, _ = GinkgoWriter.Write([]byte("==============================Global FIRST Node Synchronized Before Each==============================")) - SetDefaultEventuallyTimeout(CFEventuallyTimeout) - SetDefaultConsistentlyDuration(CFConsistentlyTimeout) + var _ = SynchronizedBeforeSuite(func() []byte { + _, _ = GinkgoWriter.Write([]byte("==============================Global FIRST Node Synchronized Before Each==============================")) + SetDefaultEventuallyTimeout(CFEventuallyTimeout) + SetDefaultConsistentlyDuration(CFConsistentlyTimeout) - helpers.SetupSynchronizedSuite(func() { - helpers.EnableFeatureFlag("diego_docker") - helpers.EnableFeatureFlag("service_instance_sharing") - }) + helpers.SetupSynchronizedSuite(func() { + helpers.EnableFeatureFlag("diego_docker") + helpers.EnableFeatureFlag("service_instance_sharing") + helpers.EnableFeatureFlag("route_sharing") + }) - _, _ = GinkgoWriter.Write([]byte("==============================End of Global FIRST Node Synchronized Before Each==============================")) + _, _ = GinkgoWriter.Write([]byte("==============================End of Global FIRST Node Synchronized Before Each==============================")) - return nil - }, func(_ []byte) { - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized Before Each==============================", GinkgoParallelNode()))) - // Ginkgo Globals - SetDefaultEventuallyTimeout(CFEventuallyTimeout) - SetDefaultConsistentlyDuration(CFConsistentlyTimeout) + return nil + }, func(_ []byte) { + _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized Before Each==============================", GinkgoParallelNode()))) + // Ginkgo Globals + SetDefaultEventuallyTimeout(CFEventuallyTimeout) + SetDefaultConsistentlyDuration(CFConsistentlyTimeout) - // Setup common environment variables - helpers.TurnOffColors() + // Setup common environment variables + helpers.TurnOffColors() - *readOnlyOrg, *readOnlySpace = helpers.SetupReadOnlyOrgAndSpace() - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized Before Each==============================", GinkgoParallelNode()))) - }) + *readOnlyOrg, *readOnlySpace = helpers.SetupReadOnlyOrgAndSpace() + _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized Before Each==============================", GinkgoParallelNode()))) + }) - var _ = SynchronizedAfterSuite(func() { - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized After Each==============================", GinkgoParallelNode()))) - *homeDir = helpers.SetHomeDir() - helpers.SetAPI() - helpers.LoginCF() - helpers.QuickDeleteOrg(*readOnlyOrg) - helpers.DestroyHomeDir(*homeDir) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized After Each==============================", GinkgoParallelNode()))) - }, func() {}) + var _ = SynchronizedAfterSuite(func() { + _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized After Each==============================", GinkgoParallelNode()))) + *homeDir = helpers.SetHomeDir() + helpers.SetAPI() + helpers.LoginCF() + helpers.QuickDeleteOrg(*readOnlyOrg) + helpers.DestroyHomeDir(*homeDir) + _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized After Each==============================", GinkgoParallelNode()))) + }, func() {}) - var _ = BeforeEach(func() { - _, _ = GinkgoWriter.Write([]byte("==============================Global Before Each==============================")) - *homeDir = helpers.SetHomeDir() - *apiURL, *skipSSLValidation = helpers.SetAPI() - _, _ = GinkgoWriter.Write([]byte("==============================End of Global Before Each==============================")) - }) + var _ = BeforeEach(func() { + _, _ = GinkgoWriter.Write([]byte("==============================Global Before Each==============================")) + *homeDir = helpers.SetHomeDir() + *apiURL, *skipSSLValidation = helpers.SetAPI() + _, _ = GinkgoWriter.Write([]byte("==============================End of Global Before Each==============================")) + }) - var _ = AfterEach(func() { - _, _ = GinkgoWriter.Write([]byte("==============================Global After Each==============================\n")) - helpers.DestroyHomeDir(*homeDir) - _, _ = GinkgoWriter.Write([]byte("==============================End of Global After Each==============================")) - }) + var _ = AfterEach(func() { + _, _ = GinkgoWriter.Write([]byte("==============================Global After Each==============================\n")) + helpers.DestroyHomeDir(*homeDir) + _, _ = GinkgoWriter.Write([]byte("==============================End of Global After Each==============================")) + }) - return nil + return nil } From c5839d5b757b15104c6d6f8c4f84af554feadf01 Mon Sep 17 00:00:00 2001 From: "M. Oleske" Date: Thu, 13 Oct 2022 13:29:23 -0700 Subject: [PATCH 045/543] Replace ioutil.ReadFile with embed for fixtures - fake_data.go tries to load files for testing, but has an assumption that code lives on the gopath - this is no longer case with newer go versions - use embed to embed the fixtures directory into the file which then allows relative path loading --- integration/helpers/fake_data.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/integration/helpers/fake_data.go b/integration/helpers/fake_data.go index b3740864fd9..88e378a765e 100644 --- a/integration/helpers/fake_data.go +++ b/integration/helpers/fake_data.go @@ -1,16 +1,16 @@ package helpers import ( + "embed" "fmt" - "io/ioutil" - "net/http" - "os" - "path/filepath" - . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" + "net/http" ) +//go:embed fixtures +var content embed.FS + // AddFiftyOneOrgs adds a mock handler to the given server which returns // 51 orgs on GET requests to /v3/organizations?order_by=name. It also // paginates, so page 2 can be requested with /v3/organizations?page=2&per_page=50. @@ -81,9 +81,7 @@ func AddEmptyPaginatedResponse(server *ghttp.Server, path string) { } func fixtureData(name string) []byte { - wd := os.Getenv("GOPATH") - fp := filepath.Join(wd, "src", "code.cloudfoundry.org", "cli", "integration", "helpers", "fixtures", name) - b, err := ioutil.ReadFile(fp) + b, err := content.ReadFile("fixtures/" + name) Expect(err).ToNot(HaveOccurred()) return b } From 2201c608a95c65da071fc264cb3f4477d58cb6b6 Mon Sep 17 00:00:00 2001 From: Al Berez Date: Fri, 21 Oct 2022 15:48:32 -0700 Subject: [PATCH 046/543] Add Gitpod (#2339) * Add cli-ci into the workspace * Preinstall packages * Checkout cli-private repo * Add asdf * Add bosh tooling --- .gitpod.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000000..f03a180c657 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,28 @@ + +additionalRepositories: + - url: https://github.com/cloudfoundry/cli-ci + checkoutLocation: cli-ci + +tasks: + - name: Setup Workspace + before: > + sudo apt install --yes + icdiff + tldr + fzf + + brew install + asdf + cloudfoundry/tap/credhub-cli + cloudfoundry/tap/bbl + cloudfoundry/tap/bosh-cli + init: > + tldr --update + + make clean build + command: out/cf version + +vscode: + extensions: + - vscodevim.vim + - eamodio.gitlens From 94933d7bc9db42526cb293fa8b98a7e5554aae76 Mon Sep 17 00:00:00 2001 From: Ryker Reed Date: Sun, 23 Oct 2022 20:02:31 -0400 Subject: [PATCH 047/543] Fix references to default branch to use main. --- .github/CONTRIBUTING.md | 4 +-- .github/PULL_REQUEST_TEMPLATE.md | 4 +-- .github/license/CF_NOTICE | 12 ++++---- .github/workflows/code-quality.yml | 4 +-- .github/workflows/integration.yml | 4 +-- .travis.yml | 2 +- README.md | 6 ++-- bin/push.sh | 10 +++---- ...elop-v6-and-v7-on-master-simultaneously.md | 8 ++--- doc/adr/0010-v7-and-v8.md | 14 ++++----- integration/shared/plugin/help_test.go | 2 +- plugin/plugin_examples/CHANGELOG.md | 8 ++--- plugin/plugin_examples/DOC.md | 24 +++++++-------- plugin/plugin_examples/README.md | 30 +++++++++---------- 14 files changed, 66 insertions(+), 66 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c37025fe4b0..8bebe8ec364 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -8,7 +8,7 @@ mailing list. ## CLI Versions The cf CLI follows a branching model: -- V9 (Next major release) of the cf CLI is built from the [master branch](https://github.com/cloudfoundry/cli/tree/master). **This branch is under active development**. +- V9 (Next major release) of the cf CLI is built from the [main branch](https://github.com/cloudfoundry/cli/tree/main). **This branch is under active development**. - V8 of the cf CLI is built from the [v8 branch](https://github.com/cloudfoundry/cli/tree/v8). **This branch is under active development**. - V7 of the cf CLI is built from the [v7 branch](https://github.com/cloudfoundry/cli/tree/v7). **This branch is maintenance only** and will only be updated to patch CVEs and very severe blocking defects. - V6 of the cf CLI is built from the [v6 branch](https://github.com/cloudfoundry/cli/tree/v6). **This branch is maintenance only** and will only be updated to patch CVEs and very severe blocking defects. @@ -37,7 +37,7 @@ After reaching out to the CLI team and the conclusion is to make a PR, please fo * Follow the other sections on this page to [set up your development environment](#development-environment-setup), [build `cf`](#building-the-cf-binary) and [run the tests](#testing). * Tests are required for any changes. 1. Push to your fork (e.g. `git push origin better_cli`) and [submit a pull request](https://help.github.com/articles/creating-a-pull-request) -1. The cf CLI team will merge your changes from the versioned branch (e.g. v7) to master for you after the PR is merged. +1. The cf CLI team will merge your changes from the versioned branch (e.g. v7) to main for you after the PR is merged. Note: All contributions must be sent using GitHub Pull Requests. We prefer a small, focused pull request with a clear message diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a698e6eb211..657ec86f60a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,11 @@ Thank you for contributing to the CF CLI! Please read the following: -* Please make sure you have implemented changes in line with the [contributing guidelines](https://github.com/cloudfoundry/cli/blob/master/.github/CONTRIBUTING.md) +* Please make sure you have implemented changes in line with the [contributing guidelines](https://github.com/cloudfoundry/cli/blob/main/.github/CONTRIBUTING.md) * We're not allowed to accept any PRs without a signed CLA, no matter how small. If your contribution falls under a company CLA but your membership is not public, expect delays while we confirm. * All new code requires tests to protect against regressions. -* Contributions must be made against the appropriate branch. See the [contributing guidelines](https://github.com/cloudfoundry/cli/blob/master/.github/CONTRIBUTING.md) +* Contributions must be made against the appropriate branch. See the [contributing guidelines](https://github.com/cloudfoundry/cli/blob/main/.github/CONTRIBUTING.md) * Contributions must conform to our [style guide](https://github.com/cloudfoundry/cli/wiki/CLI-Product-Specific-Style-Guide). Please reach out to us if you have questions. diff --git a/.github/license/CF_NOTICE b/.github/license/CF_NOTICE index 2224b281c8e..280be3690b2 100644 --- a/.github/license/CF_NOTICE +++ b/.github/license/CF_NOTICE @@ -9,28 +9,28 @@ You may not use this project except in compliance with the License. Attribution notices: -This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/code.cloudfoundry.org/gofileutils/fileutils that is: +This product includes software from https://github.com/cloudfoundry/cli/tree/main/vendor/code.cloudfoundry.org/gofileutils/fileutils that is: Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. Copyright (c) 2014-2015 Pivotal Software, Inc. and is licensed under the Apache License, Version 2.0. -This product includes software from https://github.com/code.cloudfoundry.org/cli/tree/master/vendor/code.cloudfoundry.org/ykk that is: +This product includes software from https://github.com/code.cloudfoundry.org/cli/tree/main/vendor/code.cloudfoundry.org/ykk that is: Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. and is licensed under the Apache License, Version 2.0. -This product includes software from https://github.com/code.cloudfoundry.org/cli/tree/master/vendor/code.cloudfoundry.org/cfnetworking-cli-api that is: +This product includes software from https://github.com/code.cloudfoundry.org/cli/tree/main/vendor/code.cloudfoundry.org/cfnetworking-cli-api that is: Copyright (c) 2016-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. and is licensed under the Apache License, Version 2.0. -This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/github.com/cloudfoundry/cli-plugin-repo/web that is: +This product includes software from https://github.com/cloudfoundry/cli/tree/main/vendor/github.com/cloudfoundry/cli-plugin-repo/web that is: Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. Copyright (c) 2015 Pivotal Software, Inc. and is licensed under the Apache License, Version 2.0. -This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/github.com/cloudfoundry/dropsonde that is: +This product includes software from https://github.com/cloudfoundry/cli/tree/main/vendor/github.com/cloudfoundry/dropsonde that is: Copyright (c) 2014-2015 Pivotal Software, Inc. and is licensed under the Apache License, Version 2.0. -This product includes software from https://github.com/cloudfoundry/cli/tree/master/vendor/github.com/docker/docker/pkg/term that is: +This product includes software from https://github.com/cloudfoundry/cli/tree/main/vendor/github.com/docker/docker/pkg/term that is: Copyright 2012-2016 Docker, Inc. and is licensed under the Apache License, Version 2.0. diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index b37b1bef652..ad0422f190c 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -2,9 +2,9 @@ name: "Code Quality" on: push: - branches: [ master, '*' ] + branches: [ main, '*' ] pull_request: - branches: [ master ] + branches: [ main ] schedule: - cron: '45 5 * * *' diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 9817595250a..d611ca103fb 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: pull_request: branches: - - master + - main - v8 - v7 paths-ignore: @@ -13,7 +13,7 @@ on: - 'README.md' push: branches: - - master + - main - v8 - v7 paths-ignore: diff --git a/.travis.yml b/.travis.yml index bcd768fece3..662006ed145 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ script: - NODES=1 make units-full branches: only: - - master + - main - travis os: - linux diff --git a/README.md b/README.md index 8d4d7874481..2fdc8dfbdcd 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ View the latest help for [**The v8 CLI**](https://cli.cloudfoundry.org/en-US/v8) [![Documentation](https://img.shields.io/badge/docs-online-ff69b4.svg)](https://docs.cloudfoundry.org/cf-cli) [![Command help pages](https://img.shields.io/badge/command-help-lightgrey.svg)](https://cli.cloudfoundry.org) [![Slack](https://slack.cloudfoundry.org/badge.svg)](https://slack.cloudfoundry.org) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/cloudfoundry/cli/blob/master/LICENSE) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/cloudfoundry/cli/blob/main/LICENSE) CF CLI Binary Download Server's uptime: @@ -55,7 +55,7 @@ on [Core CF CLI Pivotal Tracker](https://www.pivotaltracker.com/n/projects/89293 ## Contributing & Build Instructions Please read the [contributors' guide](.github/CONTRIBUTING.md) -If you'd like to submit updated translations, please see the [i18n README](https://github.com/cloudfoundry/cli/blob/master/cf/i18n/README-i18n.md) for instructions on how to submit an update. +If you'd like to submit updated translations, please see the [i18n README](https://github.com/cloudfoundry/cli/blob/main/cf/i18n/README-i18n.md) for instructions on how to submit an update. ![Example](.github/cf_example.gif) @@ -94,7 +94,7 @@ If the error remains or feature still missing, check the [open issues](https://g The CF CLI supports external code execution via the plugins API. For more information follow: -* [The CF CLI plugin development guide](https://github.com/cloudfoundry/cli/tree/master/plugin/plugin_examples) +* [The CF CLI plugin development guide](https://github.com/cloudfoundry/cli/tree/main/plugin/plugin_examples) * [The official plugins repository](https://plugins.cloudfoundry.org/) When importing the plugin code use `import "code.cloudfoundry.org/cli/plugin"`. diff --git a/bin/push.sh b/bin/push.sh index a6b7a23703a..ca97194fbba 100755 --- a/bin/push.sh +++ b/bin/push.sh @@ -4,8 +4,8 @@ set -e git pull -r branch_name=$(git rev-parse --abbrev-ref HEAD) -if [[ "$branch_name" == "master" ]]; then - printf "It looks like you are committing to master. If this is something that needs to be backported, please make the commit there and then use this script to merge it forward.\n Do you want to continue? [y/N]:" +if [[ "$branch_name" == "main" ]]; then + printf "It looks like you are committing to main. If this is something that needs to be backported, please make the commit there and then use this script to merge it forward.\n Do you want to continue? [y/N]:" read input if [[ "$input" == "y" || "$input" == "Y" ]]; then @@ -15,12 +15,12 @@ if [[ "$branch_name" == "master" ]]; then fi git push -printf "Do you want to merge this commit forward to master? [y/N]: " +printf "Do you want to merge this commit forward to main? [y/N]: " read input if [[ "$input" == "y" || "$input" == "Y" ]]; then - git rebase origin/master - git checkout master + git rebase origin/main + git checkout main git pull -r git merge $branch_name make clean && make build diff --git a/doc/adr/0002-develop-v6-and-v7-on-master-simultaneously.md b/doc/adr/0002-develop-v6-and-v7-on-master-simultaneously.md index 6fa511cc943..8a7c2fba7b4 100644 --- a/doc/adr/0002-develop-v6-and-v7-on-master-simultaneously.md +++ b/doc/adr/0002-develop-v6-and-v7-on-master-simultaneously.md @@ -1,4 +1,4 @@ -# 2. Develop v6 and v7 on master simultaneously +# 2. Develop v6 and v7 on main simultaneously Date: 2018-09-25 @@ -18,12 +18,12 @@ VAT is doing so in v7 releases of the CLI. For the last three weeks, VAT has been developing on a long-running _vat_ branch based on v6.39. Part of the goals for VAT include -incorporating all 7-10 new epics that will be built on _master_, +incorporating all 7-10 new epics that will be built on _main_, along with sharing any bugfixes back and forth between the two branches. VAT has been tracking which commits should be cherry-picked back and forth each day, resulting in significant overhead and bookkeeping. Rather than continuously merging two long-running branches into each other, -the VAT team proposes to develop on _master_ simultaneously with CLI. +the VAT team proposes to develop on _main_ simultaneously with CLI. This would allow the CAPI v3 acceleration work to proceed faster. The CLI team is constrained by needing to continue shipping stable versions of v6. @@ -37,7 +37,7 @@ and would reap most of the benefit. _The change that we're proposing or have agreed to implement._ -VAT is proposing to have both teams develop in the _master_ branch simultaneously. +VAT is proposing to have both teams develop in the _main_ branch simultaneously. One code base would be building two different versions of the binary. The v6 binary would not include any v7 actors or commands. diff --git a/doc/adr/0010-v7-and-v8.md b/doc/adr/0010-v7-and-v8.md index 8cb689244d5..9aecde432dc 100644 --- a/doc/adr/0010-v7-and-v8.md +++ b/doc/adr/0010-v7-and-v8.md @@ -1,4 +1,4 @@ -# 10. Synchronizing V7 and V8 (Master) code +# 10. Synchronizing V7 and V8 (Main) code Date: 2020-06-04 @@ -12,7 +12,7 @@ _The issue motivating this decision, and any context that influences or constrai **Current**: Changes committed to the [V7 branch](https://github.com/cloudfoundry/cli/tree/v7) need to be cherry-picked -onto the [master (V8) branch](https://github.com/cloudfoundry/cli), a manual +onto the [main (V8) branch](https://github.com/cloudfoundry/cli), a manual process. This depends on the pair to remember to cherry-pick. If forgotten, the commit @@ -23,7 +23,7 @@ manifested in both branches. _The change that we're proposing or have agreed to implement._ -Commit changes on the v7 branch then merge to master/v8, e.g.: +Commit changes on the v7 branch then merge to main/v8, e.g.: ```bash git co v7 @@ -35,8 +35,8 @@ git ci bin/push.sh ``` -[`push.sh`](https://github.com/cloudfoundry/cli/blob/master/bin/push.sh) is a -scripts which pushes the v7 commit, rebases against master, checks out master, +[`push.sh`](https://github.com/cloudfoundry/cli/blob/main/bin/push.sh) is a +scripts which pushes the v7 commit, rebases against main, checks out main, pulls, merges v7, pushes, and then checks out v7. It automates some of the more tedious aspects of the commit cycle. @@ -54,8 +54,8 @@ It is not without drawbacks, though. Here are the notes from the Ops Mgr anchor: _What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated._ -If a pair forgets to merge their commit to master, the subsequent merge to -master will pick up that change (this is a good thing). +If a pair forgets to merge their commit to main, the subsequent merge to +main will pick up that change (this is a good thing). Sometimes a merge conflict will occur. This happens with cherry-picks, too. diff --git a/integration/shared/plugin/help_test.go b/integration/shared/plugin/help_test.go index de5b59510f9..649ae6c3486 100644 --- a/integration/shared/plugin/help_test.go +++ b/integration/shared/plugin/help_test.go @@ -18,7 +18,7 @@ var _ = Describe("help", func() { uninstallTestPlugin() }) - It("displays the plugin commands in master help", func() { + It("displays the plugin commands in main help", func() { session := helpers.CF("help") Eventually(session).Should(Say("TestPluginCommandWithAlias")) Eventually(session).Should(Exit(0)) diff --git a/plugin/plugin_examples/CHANGELOG.md b/plugin/plugin_examples/CHANGELOG.md index 5a93397074f..7e5921dfe81 100644 --- a/plugin/plugin_examples/CHANGELOG.md +++ b/plugin/plugin_examples/CHANGELOG.md @@ -1,4 +1,4 @@ -[Go here for documentation of the plugin API](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/DOC.md) +[Go here for documentation of the plugin API](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/DOC.md) # Changes in v6.25.0 - `GetApp` now returns `Path` and `Port` information. @@ -11,7 +11,7 @@ # Changes in v6.14.0 - API `AccessToken()` now provides a refreshed o-auth token. -- [Examples](https://github.com/cloudfoundry/cli/tree/master/plugin/plugin_examples#test-driven-development-tdd) on how to use fake `CliConnection` and test RPC server for TDD development. +- [Examples](https://github.com/cloudfoundry/cli/tree/main/plugin/plugin_examples#test-driven-development-tdd) on how to use fake `CliConnection` and test RPC server for TDD development. - Fix Plugin API file descriptors leakage. - Fix bug where some CLI versions does not respect `PluginMetadata.MinCliVersion`. - The field `PackageUpdatedAt` returned by `GetApp()` API is now populated. @@ -73,9 +73,9 @@ Added the following commands to cli_connection.go: - Plugins in the community repo can be browsed and installed from the CLI # Changes in v6.9.0 -- Plugins can now have versions, i.e. 1.2.3, [code example](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/basic_plugin.go) +- Plugins can now have versions, i.e. 1.2.3, [code example](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/basic_plugin.go) - `cf plugins` now displays plugin versions -- `-h` and `--help` flags work with plugin commands. e.g. `cf -h`. [code example](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/echo.go) +- `-h` and `--help` flags work with plugin commands. e.g. `cf -h`. [code example](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/echo.go) - Allow `cf help ` # Changes in v6.8.0 diff --git a/plugin/plugin_examples/DOC.md b/plugin/plugin_examples/DOC.md index 37566597081..78783e593ef 100644 --- a/plugin/plugin_examples/DOC.md +++ b/plugin/plugin_examples/DOC.md @@ -81,15 +81,15 @@ GetService(serviceInstance string) (plugin_models.GetService_Model, error) ``` --- Models return from APIs -- [Organization](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_current_org.go#L3) -- [Space](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_current_space.go#L3) -- [GetApp_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_app.go#L5) -- [GetApps_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_apps.go#L3) -- [GetOrgs_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_orgs.go#L3) -- [GetOrg_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_org.go#L3) -- [GetSpaces_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_spaces.go#L3) -- [GetSpace_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_space.go#L3) -- [GetOrgUsers_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_org_users.go#L3) -- [GetSpaceUsers_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_space_users.go#L3) -- [GetServices_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_services.go#L3) -- [GetService_Model](https://github.com/cloudfoundry/cli/blob/master/plugin/models/get_service.go#L3) +- [Organization](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_current_org.go#L3) +- [Space](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_current_space.go#L3) +- [GetApp_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_app.go#L5) +- [GetApps_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_apps.go#L3) +- [GetOrgs_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_orgs.go#L3) +- [GetOrg_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_org.go#L3) +- [GetSpaces_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_spaces.go#L3) +- [GetSpace_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_space.go#L3) +- [GetOrgUsers_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_org_users.go#L3) +- [GetSpaceUsers_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_space_users.go#L3) +- [GetServices_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_services.go#L3) +- [GetService_Model](https://github.com/cloudfoundry/cli/blob/main/plugin/models/get_service.go#L3) diff --git a/plugin/plugin_examples/README.md b/plugin/plugin_examples/README.md index 44a088f8e18..10758d7787d 100644 --- a/plugin/plugin_examples/README.md +++ b/plugin/plugin_examples/README.md @@ -14,15 +14,15 @@ Our current plugin architecture currently requires a review and possible large o # Changes in v6.14.0 - API `AccessToken()` now provides a refreshed o-auth token. -- [Examples](https://github.com/cloudfoundry/cli/tree/master/plugin/plugin_examples#test-driven-development-tdd) on how to use fake `CliConnection` and test RPC server for TDD development. +- [Examples](https://github.com/cloudfoundry/cli/tree/main/plugin/plugin_examples#test-driven-development-tdd) on how to use fake `CliConnection` and test RPC server for TDD development. - Fix Plugin API file descriptors leakage. - Fix bug where some CLI versions does not respect `PluginMetadata.MinCliVersion`. - The field `PackageUpdatedAt` returned by `GetApp()` API is now populated. -[Complete change log ...](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/CHANGELOG.md) +[Complete change log ...](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/CHANGELOG.md) # Developing a Plugin -[Go here for documentation of the plugin API](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/DOC.md) +[Go here for documentation of the plugin API](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/DOC.md) This README discusses how to develop a cf CLI plugin. For user-focused documentation, see [Using the cf CLI](http://docs.cloudfoundry.org/cf-cli/use-cli-plugins.html). @@ -50,26 +50,26 @@ Here is an illustration of the work flow when a plugin command is being invoked. 1: CLI launches 2 processes, the rpc server and the independent plugin executable

-workflow 1 +workflow 1

2: Plugin establishes a connection to the RPC server, the connection is used to invoke core cli commands.

-workflow 1 +workflow 1

3: When a plugin invokes a cli command, it talks to the rpc server, and the rpc server interacts with cf cli to perform the command. The result is passed back to the plugin through the rpc server.

-workflow 1 +workflow 1

- Plugins that you develop for the cf CLI must conform to a predefined plugin interface that we discuss below. ## Writing a Plugin -[Go here for documentation of the plugin API](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/DOC.md) +[Go here for documentation of the plugin API](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/DOC.md) -To write a plugin for the cf CLI, implement the [predefined plugin interface](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin.go). +To write a plugin for the cf CLI, implement the [predefined plugin interface](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin.go). The interface uses a `Run(...)` method as the main entry point between the CLI and a plugin. This method receives the following arguments: @@ -82,21 +82,21 @@ Plugin names with spaces must be enclosed in quotes when installed and uninstall To initialize a plugin, call `plugin.Start(new(MyPluginStruct))` from within the `main()` method of your plugin. The `plugin.Start(...)` function requires a new reference to the struct that implements the defined interface. -This repo contains a basic plugin example [here](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/basic_plugin.go).
-To see more examples, go [here](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/). +This repo contains a basic plugin example [here](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/basic_plugin.go).
+To see more examples, go [here](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/). ### Uninstalling A Plugin Uninstall of the plugin needs to be explicitly handled. When a user calls the `cf uninstall-plugin` command, CLI notifies the plugin via a call with `CLI-MESSAGE-UNINSTALL` as the first item in `[]args` from within the plugin's `Run(...)` method. ### Test Driven Development (TDD) An example which was developed using TDD is available: -- `Test RPC server`: an RPC server to be used as a back-end for the plugin. It allows the plugin to be tested as a stand alone binary without replying on CLI as a back-end. [See example](https://github.com/cloudfoundry/cli/tree/master/plugin/plugin_examples/test_rpc_server_example) +- `Test RPC server`: an RPC server to be used as a back-end for the plugin. It allows the plugin to be tested as a stand alone binary without replying on CLI as a back-end. [See example](https://github.com/cloudfoundry/cli/tree/main/plugin/plugin_examples/test_rpc_server_example) ### Using Command Line Arguments The `Run(...)` method accepts the command line arguments and flags that you define for a plugin. - See the [command line arguments example] (https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/echo.go) included in this repo. + See the [command line arguments example] (https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/echo.go) included in this repo. #### Global Flags There are several global flags that will not be passed to the plugin. These are: @@ -109,15 +109,15 @@ You can invoke CLI commands with `cliConnection.CliCommand([]args)` from within The `cliConnection.CliCommand([]args)` returns the output printed by the command and an error. The output is returned as a slice of strings. The error will be present if the call to the CLI command fails. -See the [test plugin example](https://github.com/cloudfoundry/cli/blob/master/integration/assets/test_plugin/test_plugin.go) included in this repo. +See the [test plugin example](https://github.com/cloudfoundry/cli/blob/main/integration/assets/test_plugin/test_plugin.go) included in this repo. ### Creating Interactive Plugins -Because a plugin has access to stdin during a call to the `Run(...)` method, you can create interactive plugins. See the [interactive plugin example](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/interactive.go) included in this repo. +Because a plugin has access to stdin during a call to the `Run(...)` method, you can create interactive plugins. See the [interactive plugin example](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/interactive.go) included in this repo. ### Creating Plugins with multiple commands -A single plugin binary can have more than one command, and each command can have it's own help text defined. For an example of multi-command plugins, see the [multiple commands example](https://github.com/cloudfoundry/cli/blob/master/plugin/plugin_examples/multiple_commands.go) +A single plugin binary can have more than one command, and each command can have it's own help text defined. For an example of multi-command plugins, see the [multiple commands example](https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/multiple_commands.go) ### Enforcing a minimum CLI version required for the plugin. From 5054357fb044524cd375be374b03b3bab4b6fdf2 Mon Sep 17 00:00:00 2001 From: Simon Tremblay Date: Mon, 21 Nov 2022 14:00:17 -0500 Subject: [PATCH 048/543] [gh-2333] fix CVE on main branch --- cf/ssh/ssh.go | 2 +- cf/ssh/ssh_test.go | 2 +- cf/ssh/terminal/helper.go | 2 +- .../terminalfakes/fake_terminal_helper.go | 2 +- go.mod | 18 +++++----- go.sum | 34 +++++++++---------- .../clisshfakes/fake_terminal_helper.go | 2 +- util/clissh/ssh.go | 2 +- util/clissh/ssh_test.go | 2 +- util/clissh/terminal_helper.go | 2 +- 10 files changed, 33 insertions(+), 35 deletions(-) diff --git a/cf/ssh/ssh.go b/cf/ssh/ssh.go index b1bf62f12b0..68fbd5bf50b 100644 --- a/cf/ssh/ssh.go +++ b/cf/ssh/ssh.go @@ -23,7 +23,7 @@ import ( "code.cloudfoundry.org/cli/cf/ssh/options" "code.cloudfoundry.org/cli/cf/ssh/sigwinch" "code.cloudfoundry.org/cli/cf/ssh/terminal" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" ) const ( diff --git a/cf/ssh/ssh_test.go b/cf/ssh/ssh_test.go index 76b23f26f78..b6ef1094f02 100644 --- a/cf/ssh/ssh_test.go +++ b/cf/ssh/ssh_test.go @@ -30,7 +30,7 @@ import ( "code.cloudfoundry.org/diego-ssh/test_helpers/fake_ssh" "code.cloudfoundry.org/lager/lagertest" "github.com/kr/pty" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" "golang.org/x/crypto/ssh" . "github.com/onsi/ginkgo" diff --git a/cf/ssh/terminal/helper.go b/cf/ssh/terminal/helper.go index b755b3ea988..b4d52a84421 100644 --- a/cf/ssh/terminal/helper.go +++ b/cf/ssh/terminal/helper.go @@ -3,7 +3,7 @@ package terminal import ( "io" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" ) //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . TerminalHelper diff --git a/cf/ssh/terminal/terminalfakes/fake_terminal_helper.go b/cf/ssh/terminal/terminalfakes/fake_terminal_helper.go index 8a7ed1a2e2e..82bb9d228bc 100644 --- a/cf/ssh/terminal/terminalfakes/fake_terminal_helper.go +++ b/cf/ssh/terminal/terminalfakes/fake_terminal_helper.go @@ -6,7 +6,7 @@ import ( "sync" "code.cloudfoundry.org/cli/cf/ssh/terminal" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" ) type FakeTerminalHelper struct { diff --git a/go.mod b/go.mod index 5169eb5ea3c..198599c2497 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-runewidth v0.0.5-0.20181218000649-703b5e6b11ae github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2 - github.com/moby/moby v1.4.2-0.20171120205147-9de84a78d76e + github.com/moby/term v0.0.0-20221120202655-abb19827d345 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 @@ -40,8 +40,8 @@ require ( github.com/tedsuo/rata v1.0.1-0.20170830210128-07d200713958 github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/net v0.0.0-20220531201128-c960675eff93 - golang.org/x/text v0.3.7 + golang.org/x/net v0.2.0 + golang.org/x/text v0.4.0 gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/yaml.v2 v2.4.0 k8s.io/apimachinery v0.22.2 @@ -50,7 +50,7 @@ require ( require ( cloud.google.com/go v0.65.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.18 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect @@ -63,7 +63,6 @@ require ( github.com/cloudfoundry/bosh-utils v0.0.0-20180315210917-c6a922e299b8 // indirect github.com/cppforlife/go-patch v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/docker v1.4.2-0.20171120205147-9de84a78d76e // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-logr/logr v0.4.0 // indirect @@ -82,13 +81,12 @@ require ( github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00 // indirect - golang.org/x/mod v0.4.2 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/sys v0.2.0 // indirect + golang.org/x/term v0.2.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/tools v0.1.12 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect google.golang.org/grpc v1.41.0 // indirect diff --git a/go.sum b/go.sum index dc1993b5db4..72506e6bf12 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ code.cloudfoundry.org/tlsconfig v0.0.0-20210615191307-5d92ef3894a7/go.mod h1:CKI code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d h1:M+zXqtXJqcsmpL76aU0tdl1ho23eYa4axYoM4gD62UA= code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d/go.mod h1:YUJiVOr5xl0N/RjMxM1tHmgSpBbi5UM+KoVR5AoejO0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= @@ -122,6 +122,7 @@ github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2 github.com/cppforlife/go-patch v0.1.0 h1:I0fT+gFTSW4xWwvaTaUUVjr9xxjNXJ4naGc01BeQjwY= github.com/cppforlife/go-patch v0.1.0/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/cyphar/filepath-securejoin v0.2.1 h1:5DPkzz/0MwUpvR4fxASKzgApeq2OMFY5FfYtrX28Coo= github.com/cyphar/filepath-securejoin v0.2.1/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -129,8 +130,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY= github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.4.2-0.20171120205147-9de84a78d76e h1:M+/1NNHE/mg+RUox/04+rZoahJVklPfs6xZFECVnxso= -github.com/docker/docker v1.4.2-0.20171120205147-9de84a78d76e/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/drewolson/testflight v1.0.0/go.mod h1:t9oKuuEohRGLb80SWX+uxJHuhX98B7HnojqtW+Ryq30= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= @@ -289,9 +288,9 @@ github.com/mattn/go-runewidth v0.0.5-0.20181218000649-703b5e6b11ae/go.mod h1:H03 github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2 h1:g+4J5sZg6osfvEfkRZxJ1em0VT95/UOZgi/l7zi1/oE= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/moby v1.4.2-0.20171120205147-9de84a78d76e h1:6+Fs/ljqCOuJ4Ie4VUB/wQ/9oSpk2YvtSR5Clf13puk= -github.com/moby/moby v1.4.2-0.20171120205147-9de84a78d76e/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20221120202655-abb19827d345 h1:J9c53/kxIH+2nTKBEfZYFMlhghtHpIHSXpm5VRGHSnU= +github.com/moby/term v0.0.0-20221120202655-abb19827d345/go.mod h1:15ce4BGCFxt7I5NQKT+HV0yEDxmf6fSysfEDiVo3zFM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -427,8 +426,9 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180418062111-d41e8174641f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -466,8 +466,8 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA= -golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -529,13 +529,13 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -543,8 +543,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -597,8 +597,8 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c h1:C0nyHiBU2m0cR6hDiUORWqQIt3h37wsp1255QBSSXqY= -golang.org/x/tools v0.1.6-0.20210908190839-cf92b39a962c/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/util/clissh/clisshfakes/fake_terminal_helper.go b/util/clissh/clisshfakes/fake_terminal_helper.go index 941fd8e1d1a..3128914d983 100644 --- a/util/clissh/clisshfakes/fake_terminal_helper.go +++ b/util/clissh/clisshfakes/fake_terminal_helper.go @@ -6,7 +6,7 @@ import ( "sync" "code.cloudfoundry.org/cli/util/clissh" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" ) type FakeTerminalHelper struct { diff --git a/util/clissh/ssh.go b/util/clissh/ssh.go index 43fd4f1ef2a..069022872cc 100644 --- a/util/clissh/ssh.go +++ b/util/clissh/ssh.go @@ -19,7 +19,7 @@ import ( "code.cloudfoundry.org/cli/cf/ssh/sigwinch" "code.cloudfoundry.org/cli/util/clissh/ssherror" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" ) diff --git a/util/clissh/ssh_test.go b/util/clissh/ssh_test.go index 783465b9178..05cf6da870a 100644 --- a/util/clissh/ssh_test.go +++ b/util/clissh/ssh_test.go @@ -28,7 +28,7 @@ import ( "code.cloudfoundry.org/diego-ssh/test_helpers/fake_ssh" "code.cloudfoundry.org/lager/lagertest" "github.com/kr/pty" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "golang.org/x/crypto/ssh" diff --git a/util/clissh/terminal_helper.go b/util/clissh/terminal_helper.go index 092fe4915c8..88f72474144 100644 --- a/util/clissh/terminal_helper.go +++ b/util/clissh/terminal_helper.go @@ -3,7 +3,7 @@ package clissh import ( "io" - "github.com/moby/moby/pkg/term" + "github.com/moby/term" ) //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . TerminalHelper From be7b0fd5de6ec7ddcdb9f055ef8563cc6738c639 Mon Sep 17 00:00:00 2001 From: Al Berez Date: Thu, 1 Dec 2022 15:35:54 -0800 Subject: [PATCH 049/543] Trigger unit tests on pull request --- .github/workflows/units.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index 55b534dc9c0..d435e564a98 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -1,8 +1,29 @@ name: Units Tests -on: [push] +on: + workflow_dispatch: + pull_request: + branches: + - main + - v8 + - v7 + paths-ignore: + - 'doc/**' + - '.gitpod.yml' + - 'README.md' + push: + branches: + - main + - v8 + - v7 + paths-ignore: + - 'doc/**' + - '.github/**' + - '.gitpod.yml' + - 'README.md' + permissions: - contents: write + contents: read defaults: run: From af96bc2eb5698d562df8990ff02bb047149ac721 Mon Sep 17 00:00:00 2001 From: Al Berez Date: Thu, 1 Dec 2022 15:44:04 -0800 Subject: [PATCH 050/543] Let other units finish, even if one of them failed --- .github/workflows/units.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index d435e564a98..2a417bd74a7 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -68,6 +68,7 @@ jobs: name: Units needs: shared-values strategy: + fail-fast: false matrix: os: - ubuntu-latest From f773611a17174b5a10dbb7810b9f3187ab436540 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Thu, 1 Dec 2022 18:57:26 -0500 Subject: [PATCH 051/543] Spelling (#2340) * spelling: aardvark, alerts, aligns, already, application, assert, asynchronously, authentication, because, between, buildpack, buildpacks, canonical, conceptualize, confirming, connection, constructors, containing, controller, correctly, coverage, default, delete, dereferencing, describing, directory, displayed, displays, error, errors, example, executable, exist, existence, exists, explicit, extra, failure, forces, garbage, github, ignored, improperly, incur, information, input, inputted, intensity, interaction, intercept, interpolate, into, invalid, language, languages, laying-out, lifecycle, look, message, mismatch, multiple, nonexistent, nonexistenttld, not, nowhere, occurs, organization, original, output, overridden, packages, parameter, plugins, preexisting, pseudo, receive, repository, request, resource, response, retrieving, rhinoceros, running, security, selector, separate, separator, space, specified, successfully, superfluous, targeting, the, timeout, translatable, unknown, unlimited, unshare, utilities, version Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/build-sign-upload.yml | 6 +- .../app_not_found_in_manifest_error.go | 2 +- .../actionerror/no_eligible_packages_error.go | 2 +- actor/pluginaction/plugin_repository_test.go | 2 +- actor/pluginaction/uninstall_test.go | 4 +- actor/v7action/application_test.go | 2 +- actor/v7action/buildpack_test.go | 4 +- actor/v7action/domain_test.go | 4 +- actor/v7action/isolation_segment_test.go | 2 +- actor/v7action/package_test.go | 2 +- actor/v7action/security_group_test.go | 20 ++--- actor/v7action/stack_test.go | 2 +- actor/v7action/task.go | 2 +- .../handle_app_path_override_test.go | 4 +- .../handle_default_route_override_test.go | 6 +- .../handle_stack_override_test.go | 2 +- api/cloudcontroller/ccv3/internal/routing.go | 2 +- api/cloudcontroller/connection.go | 2 +- .../wrapper/request_logger_test.go | 4 +- api/plugin/wrapper/request_logger_test.go | 4 +- api/router/wrapper/request_logger_test.go | 4 +- api/uaa/client_test.go | 2 +- api/uaa/errors.go | 4 +- api/uaa/internal/routing.go | 2 +- api/uaa/wrapper/request_logger_test.go | 4 +- .../brokerbuilder/broker_builder_test.go | 2 +- .../plugin_installer_with_repo.go | 4 +- cf/api/appinstances/app_instances_test.go | 4 +- cf/api/featureflags/feature_flags_test.go | 2 +- cf/api/organizations/organizations_test.go | 2 +- cf/appfiles/cf_ignore_test.go | 2 +- cf/commandregistry/dependency_test.go | 2 +- cf/commands/application/apps_test.go | 2 +- cf/commands/application/copy_source_test.go | 2 +- cf/commands/application/push_test.go | 2 +- cf/commands/application/ssh_test.go | 2 +- .../buildpack/delete_buildpack_test.go | 2 +- cf/commands/config_test.go | 2 +- cf/commands/curl_test.go | 2 +- .../domain/delete_shared_domain_test.go | 4 +- cf/commands/login_test.go | 2 +- cf/commands/organization/create_org_test.go | 2 +- cf/commands/plugin/install_plugin_test.go | 2 +- .../pluginrepo/add_plugin_repo_test.go | 2 +- .../pluginrepo/remove_plugin_repo_test.go | 2 +- cf/commands/quota/delete_quota_test.go | 8 +- .../create_security_group_test.go | 2 +- cf/commands/service/create_service_test.go | 4 +- .../create_user_provided_service_test.go | 2 +- cf/commands/service/unbind_service_test.go | 2 +- .../update_user_provided_service_test.go | 2 +- .../disable_service_access_test.go | 2 +- .../enable_service_access_test.go | 2 +- .../spacequota/create_space_quota_test.go | 4 +- .../spacequota/delete_space_quota_test.go | 8 +- cf/commands/user/delete_user_test.go | 2 +- cf/commands/user/org_users_test.go | 6 +- cf/flags/flags_test.go | 6 +- cf/i18n/README-i18n.md | 2 +- cf/ssh/options/ssh_options_test.go | 4 +- cf/ssh/ssh_test.go | 4 +- cf/terminal/table.go | 4 +- cf/util/downloader/file_download_test.go | 2 +- cf/util/testhelpers/io/io.go | 4 +- .../matchers/match_func_name_test.go | 2 +- cf/util/testhelpers/net/server.go | 2 +- command/common/install_plugin_command.go | 2 +- .../install_plugin_from_repo_command_test.go | 2 +- command/v7/add_network_policy_command_test.go | 2 +- command/v7/auth_command.go | 2 +- command/v7/buildpacks_command_test.go | 2 +- .../v7/create_app_manifest_command_test.go | 2 +- command/v7/curl_command.go | 18 ++--- command/v7/curl_command_test.go | 30 +++---- command/v7/delete_command_test.go | 2 +- .../v7/delete_shared_domain_command_test.go | 2 +- .../v7/disable_feature_flag_command_test.go | 2 +- .../v7/enable_feature_flag_command_test.go | 2 +- command/v7/login_command_test.go | 2 +- command/v7/shared/new_clients_test.go | 2 +- command/v7/ssh_command_test.go | 4 +- doc/adr/0004-v7-push-refactor.md | 4 +- doc/adr/0007-integration-vs-command-test.md | 2 +- doc/adr/0012-de-version-v8-code-base.md | 2 +- fixtures/plugins/test_1.go | 2 +- i18n/resources/i18n_resources.go | 16 ++-- integration/helpers/login.go | 14 ++-- integration/helpers/version.go | 14 ++-- .../isolated/internationalization_test.go | 16 ++-- integration/v7/isolated/auth_command_test.go | 6 +- .../v7/isolated/create_private_domain_test.go | 2 +- .../isolated/create_service_command_test.go | 2 +- ...eate_user_provided_service_command_test.go | 4 +- .../v7/isolated/delete_command_test.go | 2 +- .../delete_orphaned_routes_command_test.go | 2 +- .../delete_private_domain_command_test.go | 4 +- .../v7/isolated/delete_space_command_test.go | 2 +- .../v7/isolated/delete_user_command_test.go | 4 +- .../v7/isolated/events_command_test.go | 2 +- .../v7/isolated/labels_command_test.go | 66 ++++++++-------- integration/v7/isolated/login_command_test.go | 4 +- .../isolated/network_policies_command_test.go | 2 +- .../v7/isolated/oauth_token_command_test.go | 4 +- .../v7/isolated/restage_command_test.go | 2 +- .../v7/isolated/revisions_command_test.go | 2 +- .../isolated/service_access_command_test.go | 18 ++--- .../v7/isolated/set_label_command_test.go | 10 +-- .../v7/isolated/share_route_command_test.go | 4 +- .../space_ssh_allowed_command_test.go | 2 +- .../v7/isolated/unset_label_command_test.go | 4 +- ...date_user_provided_service_command_test.go | 4 +- plugin/plugin_examples/basic_plugin.go | 4 +- plugin/plugin_examples/echo.go | 2 +- resources/droplet_resource.go | 2 +- util/clissh/ssh_test.go | 4 +- util/configv3/plugins_config_test.go | 2 +- util/manifest/application.go | 2 +- util/manifestparser/application.go | 2 +- util/manifestparser/application_test.go | 2 +- util/manifestparser/process.go | 2 +- util/randomword/generator.go | 4 +- util/ui/i18n.go | 2 +- util/ui/i18n_test.go | 2 +- util/ui/log_message_test.go | 6 +- util/ui/prompt.go | 10 +-- util/ui/prompt_test.go | 6 +- util/ui/table_test.go | 2 +- util/ui/ui.go | 78 +++++++++---------- util/ui/ui_for_push.go | 4 +- util/ui/ui_for_push_test.go | 30 +++---- util/ui/ui_test.go | 22 +++--- util/ui/ui_v7_test.go | 10 +-- 133 files changed, 356 insertions(+), 356 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 657ec86f60a..a3704fdaa38 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -31,7 +31,7 @@ Explain why this functionality should be in the cf CLI, as opposed to a plugin. ## Applicable Issues -List any applicable Github Issues here +List any applicable GitHub Issues here ## How Urgent Is The Change? diff --git a/.github/workflows/build-sign-upload.yml b/.github/workflows/build-sign-upload.yml index 8d66537d8ba..4a71da13489 100644 --- a/.github/workflows/build-sign-upload.yml +++ b/.github/workflows/build-sign-upload.yml @@ -54,7 +54,7 @@ jobs: echo "golang version: ${go_version[1]}" echo "::set-output name=go-version::${go_version[1]}" - # This is for debugging. It's equivalent to fly intecept + # This is for debugging. It's equivalent to fly intercept # - name: Setup upterm session # env: # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -789,12 +789,12 @@ jobs: mv cf-cli-windows-binaries/cf-cli_winx64.exe winx64/cf8.exe pushd win32 prepare_win_artifacts - # -y flag avoids the default behavior of derefencing the link, so we archive the symlink as-is + # -y flag avoids the default behavior of dereferencing the link, so we archive the symlink as-is zip -y cf8-cli_${INSTALLER_RELEASE_VERSION}_win32.zip * popd pushd winx64 prepare_win_artifacts - # -y flag avoids the default behavior of derefencing the link, so we archive the symlink as-is + # -y flag avoids the default behavior of dereferencing the link, so we archive the symlink as-is zip -y cf8-cli_${INSTALLER_RELEASE_VERSION}_winx64.zip * popd popd diff --git a/actor/actionerror/app_not_found_in_manifest_error.go b/actor/actionerror/app_not_found_in_manifest_error.go index 0c20cb04f73..ddb827e47f8 100644 --- a/actor/actionerror/app_not_found_in_manifest_error.go +++ b/actor/actionerror/app_not_found_in_manifest_error.go @@ -7,5 +7,5 @@ type AppNotFoundInManifestError struct { } func (e AppNotFoundInManifestError) Error() string { - return fmt.Sprintf("specfied app: %s not found in manifest", e.Name) + return fmt.Sprintf("specified app: %s not found in manifest", e.Name) } diff --git a/actor/actionerror/no_eligible_packages_error.go b/actor/actionerror/no_eligible_packages_error.go index 73eee7e318a..c358c4ad200 100644 --- a/actor/actionerror/no_eligible_packages_error.go +++ b/actor/actionerror/no_eligible_packages_error.go @@ -12,7 +12,7 @@ func (e NoEligiblePackagesError) Error() string { case e.AppName != "": return fmt.Sprintf("App '%s' has no eligible packages.", e.AppName) default: - return fmt.Sprintf("No eligible pacakges available for app.") + return fmt.Sprintf("No eligible packages available for app.") } } diff --git a/actor/pluginaction/plugin_repository_test.go b/actor/pluginaction/plugin_repository_test.go index d7d3956aab5..07e86248717 100644 --- a/actor/pluginaction/plugin_repository_test.go +++ b/actor/pluginaction/plugin_repository_test.go @@ -104,7 +104,7 @@ var _ = Describe("Plugin Repository Actions", func() { }) }) - When("the repository name is the same and repostiroy URL is the same except for trailing slash", func() { + When("the repository name is the same and repository URL is the same except for trailing slash", func() { BeforeEach(func() { fakeConfig.PluginRepositoriesReturns([]configv3.PluginRepository{ { diff --git a/actor/pluginaction/uninstall_test.go b/actor/pluginaction/uninstall_test.go index 895f4e2cb54..5320840f80b 100644 --- a/actor/pluginaction/uninstall_test.go +++ b/actor/pluginaction/uninstall_test.go @@ -55,8 +55,8 @@ var _ = Describe("Plugin actor", func() { }) It("returns a PluginNotFoundError", func() { - err := actor.UninstallPlugin(fakePluginUninstaller, "some-non-existent-plugin") - Expect(err).To(MatchError(actionerror.PluginNotFoundError{PluginName: "some-non-existent-plugin"})) + err := actor.UninstallPlugin(fakePluginUninstaller, "some-nonexistent-plugin") + Expect(err).To(MatchError(actionerror.PluginNotFoundError{PluginName: "some-nonexistent-plugin"})) }) }) diff --git a/actor/v7action/application_test.go b/actor/v7action/application_test.go index e360fd5dd74..d1e64b735e5 100644 --- a/actor/v7action/application_test.go +++ b/actor/v7action/application_test.go @@ -286,7 +286,7 @@ var _ = Describe("Application Actions", func() { }) It("returns an ApplicationNotFoundError and the warnings", func() { - _, warnings, err := actor.GetApplicationsByGUIDs([]string{"some-app-guid", "non-existent-app-guid"}) + _, warnings, err := actor.GetApplicationsByGUIDs([]string{"some-app-guid", "nonexistent-app-guid"}) Expect(warnings).To(ConsistOf("some-warning")) Expect(err).To(MatchError(actionerror.ApplicationsNotFoundError{})) }) diff --git a/actor/v7action/buildpack_test.go b/actor/v7action/buildpack_test.go index 22052d53b32..c4d0847497c 100644 --- a/actor/v7action/buildpack_test.go +++ b/actor/v7action/buildpack_test.go @@ -120,7 +120,7 @@ var _ = Describe("Buildpack", func() { nil) }) - It("returns warnings and a BuilpackNotFoundError", func() { + It("returns warnings and a BuildpackNotFoundError", func() { Expect(executeErr).To(MatchError(actionerror.BuildpackNotFoundError{BuildpackName: buildpackName, StackName: buildpackStack})) Expect(warnings).To(ConsistOf("some-warning-1", "some-warning-2")) }) @@ -319,7 +319,7 @@ var _ = Describe("Buildpack", func() { Describe("UpdateBuildpackByNameAndStack", func() { var ( - buildpackName = "my-buidpack" + buildpackName = "my-buildpack" buildpackStack = "my-stack" buildpack = resources.Buildpack{ Stack: "new-stack", diff --git a/actor/v7action/domain_test.go b/actor/v7action/domain_test.go index 645ac92fb25..09e4d3760a3 100644 --- a/actor/v7action/domain_test.go +++ b/actor/v7action/domain_test.go @@ -572,7 +572,7 @@ var _ = Describe("Domain Actions", func() { BeforeEach(func() { fakeCloudControllerClient.UnsharePrivateDomainFromOrgReturns( ccv3.Warnings{"unshare-domain-warning"}, - errors.New("unsahre-domain-error"), + errors.New("unshare-domain-error"), ) }) @@ -582,7 +582,7 @@ var _ = Describe("Domain Actions", func() { Expect(actualDomainGUID).To(Equal("private-domain-guid")) Expect(actualOrgGUID).To(Equal("org-guid")) - Expect(executeErr).To(MatchError(errors.New("unsahre-domain-error"))) + Expect(executeErr).To(MatchError(errors.New("unshare-domain-error"))) Expect(warnings).To(ConsistOf("get-orgs-warning", "get-domains-warning", "unshare-domain-warning")) }) }) diff --git a/actor/v7action/isolation_segment_test.go b/actor/v7action/isolation_segment_test.go index 8b8a89e7ef8..9be1a9d897f 100644 --- a/actor/v7action/isolation_segment_test.go +++ b/actor/v7action/isolation_segment_test.go @@ -711,7 +711,7 @@ var _ = Describe("Isolation Segment Actions", func() { }) }) - When("fetching the resourece fails", func() { + When("fetching the resource fails", func() { BeforeEach(func() { fakeCloudControllerClient.GetOrganizationDefaultIsolationSegmentReturns( resources.Relationship{}, diff --git a/actor/v7action/package_test.go b/actor/v7action/package_test.go index 1bcaf162c5c..57727c9e704 100644 --- a/actor/v7action/package_test.go +++ b/actor/v7action/package_test.go @@ -617,7 +617,7 @@ var _ = Describe("Package Actions", func() { Expect(tableWarnings).To(ConsistOf("some-app-warning", "some-package-warning", "upload-package-warning", "poll-package-warning", "poll-package-warning")) - // hacky, get packages is called an extry time cause the + // hacky, get packages is called an extra time cause the // JustBeforeEach executes everything once as well Expect(fakeCloudControllerClient.GetPackageCallCount()).To(Equal(3)) Expect(fakeConfig.PollingIntervalCallCount()).To(Equal(3)) diff --git a/actor/v7action/security_group_test.go b/actor/v7action/security_group_test.go index ed0a72500eb..94229fdec76 100644 --- a/actor/v7action/security_group_test.go +++ b/actor/v7action/security_group_test.go @@ -697,7 +697,7 @@ var _ = Describe("Security Group Actions", func() { ) }) - It("makes mutiple calls to get spaces", func() { + It("makes multiple calls to get spaces", func() { Expect(len(securityGroupSummaries)).To(Equal(1)) Expect(fakeCloudControllerClient.GetSpacesCallCount()).To(Equal(batches)) Expect(fakeCloudControllerClient.GetSpacesArgsForCall(0)). @@ -1038,7 +1038,7 @@ var _ = Describe("Security Group Actions", func() { }) }) - When("the seurity group is not bound to the space", func() { + When("the security group is not bound to the space", func() { BeforeEach(func() { fakeCloudControllerClient.UnbindSecurityGroupStagingSpaceReturns( ccv3.Warnings{"get-security-group-warning"}, @@ -1240,7 +1240,7 @@ var _ = Describe("Security Group Actions", func() { var ( securityGroupName = "tom" globallyEnabled bool - lifeycle constant.SecurityGroupLifecycle + lifecycle constant.SecurityGroupLifecycle executeErr error trueValue = true @@ -1248,7 +1248,7 @@ var _ = Describe("Security Group Actions", func() { ) JustBeforeEach(func() { - warnings, executeErr = actor.UpdateSecurityGroupGloballyEnabled(securityGroupName, lifeycle, globallyEnabled) + warnings, executeErr = actor.UpdateSecurityGroupGloballyEnabled(securityGroupName, lifecycle, globallyEnabled) }) When("the request succeeds", func() { @@ -1273,7 +1273,7 @@ var _ = Describe("Security Group Actions", func() { When("updating staging to true", func() { BeforeEach(func() { - lifeycle = constant.SecurityGroupLifecycleStaging + lifecycle = constant.SecurityGroupLifecycleStaging globallyEnabled = true }) @@ -1298,7 +1298,7 @@ var _ = Describe("Security Group Actions", func() { When("updating staging to false", func() { BeforeEach(func() { - lifeycle = constant.SecurityGroupLifecycleStaging + lifecycle = constant.SecurityGroupLifecycleStaging globallyEnabled = false }) @@ -1323,7 +1323,7 @@ var _ = Describe("Security Group Actions", func() { When("updating running to true", func() { BeforeEach(func() { - lifeycle = constant.SecurityGroupLifecycleRunning + lifecycle = constant.SecurityGroupLifecycleRunning globallyEnabled = true }) @@ -1348,7 +1348,7 @@ var _ = Describe("Security Group Actions", func() { When("updating running to false", func() { BeforeEach(func() { - lifeycle = constant.SecurityGroupLifecycleRunning + lifecycle = constant.SecurityGroupLifecycleRunning globallyEnabled = false }) @@ -1374,7 +1374,7 @@ var _ = Describe("Security Group Actions", func() { When("the request to get the security group errors", func() { BeforeEach(func() { - lifeycle = constant.SecurityGroupLifecycleRunning + lifecycle = constant.SecurityGroupLifecycleRunning globallyEnabled = false fakeCloudControllerClient.GetSecurityGroupsReturns( @@ -1398,7 +1398,7 @@ var _ = Describe("Security Group Actions", func() { When("the request to update the security group errors", func() { BeforeEach(func() { - lifeycle = constant.SecurityGroupLifecycleRunning + lifecycle = constant.SecurityGroupLifecycleRunning globallyEnabled = false fakeCloudControllerClient.GetSecurityGroupsReturns( diff --git a/actor/v7action/stack_test.go b/actor/v7action/stack_test.go index aaab0d4aa00..5dc3bf77e1f 100644 --- a/actor/v7action/stack_test.go +++ b/actor/v7action/stack_test.go @@ -157,7 +157,7 @@ var _ = Describe("Stack", func() { Expect(warnings).To(ConsistOf("some-stack-warning")) Expect(fakeCloudControllerClient.GetStacksCallCount()).To(Equal(1)) }) - When("a label selctor is passed in", func() { + When("a label selector is passed in", func() { BeforeEach(func() { labelSelector = "some-label-selector" }) diff --git a/actor/v7action/task.go b/actor/v7action/task.go index fd255bff205..abebb726ecf 100644 --- a/actor/v7action/task.go +++ b/actor/v7action/task.go @@ -25,7 +25,7 @@ func (actor Actor) RunTask(appGUID string, task resources.Task) (resources.Task, } // GetApplicationTasks returns a list of tasks associated with the provided -// appplication GUID. +// application GUID. func (actor Actor) GetApplicationTasks(appGUID string, sortOrder SortOrder) ([]resources.Task, Warnings, error) { tasks, warnings, err := actor.CloudControllerClient.GetApplicationTasks(appGUID) actorWarnings := Warnings(warnings) diff --git a/actor/v7pushaction/handle_app_path_override_test.go b/actor/v7pushaction/handle_app_path_override_test.go index b77a6d521dd..fba1747aead 100644 --- a/actor/v7pushaction/handle_app_path_override_test.go +++ b/actor/v7pushaction/handle_app_path_override_test.go @@ -143,7 +143,7 @@ var _ = Describe("HandleAppPathOverride", func() { parsedManifest = manifestparser.Manifest{ Applications: []manifestparser.Application{ { - Path: "some-non-existent-path", + Path: "some-nonexistent-path", }, }, } @@ -151,7 +151,7 @@ var _ = Describe("HandleAppPathOverride", func() { It("returns an error", func() { Expect(executeErr).To(MatchError(manifestparser.InvalidManifestApplicationPathError{ - Path: "some-non-existent-path", + Path: "some-nonexistent-path", })) }) }) diff --git a/actor/v7pushaction/handle_default_route_override_test.go b/actor/v7pushaction/handle_default_route_override_test.go index 3fed099f086..a33aed69aa0 100644 --- a/actor/v7pushaction/handle_default_route_override_test.go +++ b/actor/v7pushaction/handle_default_route_override_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("HandleDefualtRouteOverride", func() { +var _ = Describe("HandleDefaultRouteOverride", func() { var ( originalManifest manifestparser.Manifest transformedManifest manifestparser.Manifest @@ -57,8 +57,8 @@ var _ = Describe("HandleDefualtRouteOverride", func() { }) - // CLI doesnt know about the routes field but CAPI ignores defualt route if routes is specified - // so we are ok adding defualt route even with the presence of a routes field + // CLI doesnt know about the routes field but CAPI ignores default route if routes is specified + // so we are ok adding default route even with the presence of a routes field When("the manifest has no routing fields", func() { BeforeEach(func() { diff --git a/actor/v7pushaction/handle_stack_override_test.go b/actor/v7pushaction/handle_stack_override_test.go index 9c6d3e18752..ed79874f66c 100644 --- a/actor/v7pushaction/handle_stack_override_test.go +++ b/actor/v7pushaction/handle_stack_override_test.go @@ -37,7 +37,7 @@ var _ = Describe("HandleStackOverride", func() { } }) - It("will retain the origional stack value", func() { + It("will retain the original stack value", func() { Expect(executeErr).To(Not(HaveOccurred())) Expect(transformedManifest.Applications[0].Stack).To(Equal("og_cflinuxfs")) }) diff --git a/api/cloudcontroller/ccv3/internal/routing.go b/api/cloudcontroller/ccv3/internal/routing.go index 4b818cfca0c..b6589de43ba 100644 --- a/api/cloudcontroller/ccv3/internal/routing.go +++ b/api/cloudcontroller/ccv3/internal/routing.go @@ -12,7 +12,7 @@ import ( // Params map path keys to values. For example, if your route has the path // pattern: // /person/:person_id/pets/:pet_type -// Then a correct Params map would lool like: +// Then a correct Params map would look like: // router.Params{ // "person_id": "123", // "pet_type": "cats", diff --git a/api/cloudcontroller/connection.go b/api/cloudcontroller/connection.go index 65ab2169fdd..8d0ee03b1ae 100644 --- a/api/cloudcontroller/connection.go +++ b/api/cloudcontroller/connection.go @@ -1,4 +1,4 @@ -// Package cloudcontroller contains shared utilies between the V2 and V3 +// Package cloudcontroller contains shared utilities between the V2 and V3 // clients. // // These sets of packages are still under development/pre-pre-pre...alpha. Use diff --git a/api/cloudcontroller/wrapper/request_logger_test.go b/api/cloudcontroller/wrapper/request_logger_test.go index 81e3deb124e..920d72716f2 100644 --- a/api/cloudcontroller/wrapper/request_logger_test.go +++ b/api/cloudcontroller/wrapper/request_logger_test.go @@ -171,7 +171,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the request", func() { + When("an error occurs while trying to log the request", func() { var expectedErr error BeforeEach(func() { @@ -348,7 +348,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the response", func() { + When("an error occurs while trying to log the response", func() { var ( originalErr error expectedErr error diff --git a/api/plugin/wrapper/request_logger_test.go b/api/plugin/wrapper/request_logger_test.go index ad447373f64..377723ab538 100644 --- a/api/plugin/wrapper/request_logger_test.go +++ b/api/plugin/wrapper/request_logger_test.go @@ -160,7 +160,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the request", func() { + When("an error occurs while trying to log the request", func() { var expectedErr error BeforeEach(func() { @@ -373,7 +373,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the response", func() { + When("an error occurs while trying to log the response", func() { var ( originalErr error expectedErr error diff --git a/api/router/wrapper/request_logger_test.go b/api/router/wrapper/request_logger_test.go index ba361891961..3f7394e6ee7 100644 --- a/api/router/wrapper/request_logger_test.go +++ b/api/router/wrapper/request_logger_test.go @@ -171,7 +171,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the request", func() { + When("an error occurs while trying to log the request", func() { var expectedErr error BeforeEach(func() { @@ -305,7 +305,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the response", func() { + When("an error occurs while trying to log the response", func() { var ( originalErr error expectedErr error diff --git a/api/uaa/client_test.go b/api/uaa/client_test.go index 57afc6ee855..4e664c61922 100644 --- a/api/uaa/client_test.go +++ b/api/uaa/client_test.go @@ -51,7 +51,7 @@ var _ = Describe("UAA Client", func() { }) }) - Describe("Conection", func() { + Describe("Connection", func() { BeforeEach(func() { server.AppendHandlers( CombineHandlers( diff --git a/api/uaa/errors.go b/api/uaa/errors.go index 0fdd4287415..c4e9d2815f3 100644 --- a/api/uaa/errors.go +++ b/api/uaa/errors.go @@ -52,7 +52,7 @@ func (e RequestError) Error() string { return e.Err.Error() } -// UnauthorizedError is returned when the authentication informatin is invalid. +// UnauthorizedError is returned when the authentication information is invalid. type UnauthorizedError struct { Message string } @@ -80,7 +80,7 @@ func (e InsufficientScopeError) Error() string { return e.Message } -// InvalidSCIMResourceError is returned usually when the client tries to create an inproperly formatted username +// InvalidSCIMResourceError is returned usually when the client tries to create an improperly formatted username type InvalidSCIMResourceError struct { Message string } diff --git a/api/uaa/internal/routing.go b/api/uaa/internal/routing.go index 7c1fcad0856..7bdbba163d7 100644 --- a/api/uaa/internal/routing.go +++ b/api/uaa/internal/routing.go @@ -12,7 +12,7 @@ import ( // Params map path keys to values. For example, if your route has the path // pattern: // /person/:person_id/pets/:pet_type -// Then a correct Params map would lool like: +// Then a correct Params map would look like: // router.Params{ // "person_id": "123", // "pet_type": "cats", diff --git a/api/uaa/wrapper/request_logger_test.go b/api/uaa/wrapper/request_logger_test.go index bd19d6455a6..553349704fc 100644 --- a/api/uaa/wrapper/request_logger_test.go +++ b/api/uaa/wrapper/request_logger_test.go @@ -195,7 +195,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the request", func() { + When("an error occurs while trying to log the request", func() { var expectedErr error BeforeEach(func() { @@ -329,7 +329,7 @@ var _ = Describe("Request Logger", func() { }) }) - When("an error occures while trying to log the response", func() { + When("an error occurs while trying to log the response", func() { var ( originalErr error expectedErr error diff --git a/cf/actors/brokerbuilder/broker_builder_test.go b/cf/actors/brokerbuilder/broker_builder_test.go index 174b7e11c9c..94590c76c43 100644 --- a/cf/actors/brokerbuilder/broker_builder_test.go +++ b/cf/actors/brokerbuilder/broker_builder_test.go @@ -225,7 +225,7 @@ var _ = Describe("Broker Builder", func() { }) Describe(".GetBrokerWithSpecifiedService", func() { - It("returns an error if a broker containeing the specific service cannot be found", func() { + It("returns an error if a broker containing the specific service cannot be found", func() { serviceBuilder.GetServiceByNameWithPlansWithOrgNamesReturns(models.ServiceOffering{}, errors.New("Asplosions")) _, err := brokerBuilder.GetBrokerWithSpecifiedService("totally-not-a-service") diff --git a/cf/actors/plugininstaller/plugin_installer_with_repo.go b/cf/actors/plugininstaller/plugin_installer_with_repo.go index e089a4ebc37..50845ff4a27 100644 --- a/cf/actors/plugininstaller/plugin_installer_with_repo.go +++ b/cf/actors/plugininstaller/plugin_installer_with_repo.go @@ -41,7 +41,7 @@ func (installer *pluginInstallerWithRepo) Install(inputSourceFilepath string) st found := false sha1 := "" - for _, plugin := range findRepoCaseInsensity(pluginList, installer.RepoName) { + for _, plugin := range findRepoCaseIntensity(pluginList, installer.RepoName) { if strings.ToLower(plugin.Name) == targetPluginName { found = true outputSourceFilepath, sha1 = installer.PluginDownloader.downloadFromPlugin(plugin) @@ -73,7 +73,7 @@ func (installer *pluginInstallerWithRepo) getRepoFromConfig(repoName string) (mo return models.PluginRepo{}, errors.New(repoName + T(" not found")) } -func findRepoCaseInsensity(repoList map[string][]clipr.Plugin, repoName string) []clipr.Plugin { +func findRepoCaseIntensity(repoList map[string][]clipr.Plugin, repoName string) []clipr.Plugin { target := strings.ToLower(repoName) for k, repo := range repoList { if strings.ToLower(k) == target { diff --git a/cf/api/appinstances/app_instances_test.go b/cf/api/appinstances/app_instances_test.go index cdd41922769..b3223d37544 100644 --- a/cf/api/appinstances/app_instances_test.go +++ b/cf/api/appinstances/app_instances_test.go @@ -64,7 +64,7 @@ var _ = Describe("AppInstancesRepo", func() { It("returns the error if the response is unsuccessful", func() { ts, handler, repo := createAppInstancesRepo([]testnet.TestRequest{ - deleteInstanceFromUnkownApp, + deleteInstanceFromUnknownApp, }) defer ts.Close() appGUID := "some-wrong-app-guid" @@ -128,7 +128,7 @@ var deleteInstanceRequest = apifakes.NewCloudControllerTestRequest(testnet.TestR Response: testnet.TestResponse{Status: http.StatusNoContent, Body: `{}`}, }) -var deleteInstanceFromUnkownApp = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ +var deleteInstanceFromUnknownApp = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ Method: "DELETE", Path: "/v2/apps/some-wrong-app-guid/instances/0", Response: testnet.TestResponse{Status: http.StatusNotFound, Body: `{}`}, diff --git a/cf/api/featureflags/feature_flags_test.go b/cf/api/featureflags/feature_flags_test.go index 0ade82cca6e..de9e378f571 100644 --- a/cf/api/featureflags/feature_flags_test.go +++ b/cf/api/featureflags/feature_flags_test.go @@ -92,7 +92,7 @@ var _ = Describe("Feature Flags Repository", func() { Expect(err).ToNot(HaveOccurred()) }) - Context("when given a non-existent feature flag", func() { + Context("when given a nonexistent feature flag", func() { BeforeEach(func() { setupTestServer(featureFlagsUpdateErrorRequest) }) diff --git a/cf/api/organizations/organizations_test.go b/cf/api/organizations/organizations_test.go index ce3e458c910..c1eecc5f709 100644 --- a/cf/api/organizations/organizations_test.go +++ b/cf/api/organizations/organizations_test.go @@ -107,7 +107,7 @@ var _ = Describe("Organization Repository", func() { Expect(len(orgs)).To(Equal(3)) }) - It("lists the orgs from the the /v2/orgs endpoint in alphabetical order", func() { + It("lists the orgs from the /v2/orgs endpoint in alphabetical order", func() { orgs, apiErr := repo.ListOrgs(0) Expect(len(orgs)).To(Equal(3)) diff --git a/cf/appfiles/cf_ignore_test.go b/cf/appfiles/cf_ignore_test.go index 5f10e52377f..2814771fbcf 100644 --- a/cf/appfiles/cf_ignore_test.go +++ b/cf/appfiles/cf_ignore_test.go @@ -59,7 +59,7 @@ stuff/exclude.c`) Expect(ignore.FileShouldBeIgnored("stuff/include.c")).To(BeFalse()) }) - It("ignores certain commonly ingored files by default", func() { + It("ignores certain commonly ignored files by default", func() { ignore := NewCfIgnore(``) Expect(ignore.FileShouldBeIgnored(".git/objects")).To(BeTrue()) diff --git a/cf/commandregistry/dependency_test.go b/cf/commandregistry/dependency_test.go index 50879fbee8e..4f93d0ca41f 100644 --- a/cf/commandregistry/dependency_test.go +++ b/cf/commandregistry/dependency_test.go @@ -13,7 +13,7 @@ import ( var _ = Describe("Dependency", func() { var dependency commandregistry.Dependency - It("populates all fields by calling all the dependency contructors", func() { + It("populates all fields by calling all the dependency constructors", func() { fakeLogger := new(tracefakes.FakePrinter) dependency = commandregistry.NewDependency(os.Stdout, fakeLogger, "") diff --git a/cf/commands/application/apps_test.go b/cf/commands/application/apps_test.go index 1e12c35cbca..aaaebb32d59 100644 --- a/cf/commands/application/apps_test.go +++ b/cf/commands/application/apps_test.go @@ -197,7 +197,7 @@ var _ = Describe("list-apps command", func() { }) Context("when an app's running instances is unknown", func() { - It("dipslays a '?' for running instances", func() { + It("displays a '?' for running instances", func() { appRoutes := []models.RouteSummary{ { Host: "app1", diff --git a/cf/commands/application/copy_source_test.go b/cf/commands/application/copy_source_test.go index 5c7fb941e68..1d72044b8bf 100644 --- a/cf/commands/application/copy_source_test.go +++ b/cf/commands/application/copy_source_test.go @@ -203,7 +203,7 @@ var _ = Describe("CopySource", func() { }) Describe("when a space is provided, but not an org", func() { - It("sends the correct target appplication for the current org and target space", func() { + It("sends the correct target application for the current org and target space", func() { space := models.Space{} space.Name = "space-name" space.GUID = "model-space-guid" diff --git a/cf/commands/application/push_test.go b/cf/commands/application/push_test.go index 4ef5a3c067a..89cad1e5020 100644 --- a/cf/commands/application/push_test.go +++ b/cf/commands/application/push_test.go @@ -1764,7 +1764,7 @@ var _ = Describe("Push Command", func() { args = []string{"--no-hostname", "existing-app"} }) - It("binds the root domain route to an app with a pre-existing route", func() { + It("binds the root domain route to an app with a preexisting route", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(routeActor.FindOrCreateRouteCallCount()).To(Equal(1)) diff --git a/cf/commands/application/ssh_test.go b/cf/commands/application/ssh_test.go index fe631c61fee..d12164f3bb9 100644 --- a/cf/commands/application/ssh_test.go +++ b/cf/commands/application/ssh_test.go @@ -281,7 +281,7 @@ var _ = Describe("SSH command", func() { Context("Error when connecting", func() { It("notifies users", func() { - fakeSecureShell.ConnectReturns(errors.New("dial errorrr")) + fakeSecureShell.ConnectReturns(errors.New("dial error")) runCommand("my-app") diff --git a/cf/commands/buildpack/delete_buildpack_test.go b/cf/commands/buildpack/delete_buildpack_test.go index 26b10588c77..119c8d34872 100644 --- a/cf/commands/buildpack/delete_buildpack_test.go +++ b/cf/commands/buildpack/delete_buildpack_test.go @@ -79,7 +79,7 @@ var _ = Describe("delete-buildpack command", func() { }) Context("when the force flag is provided", func() { - It("does not prompt the user to delete the buildback", func() { + It("does not prompt the user to delete the buildpack", func() { runCommand("-f", "my-buildpack") Expect(buildpackRepo.DeleteBuildpackGUID).To(Equal("my-buildpack-guid")) diff --git a/cf/commands/config_test.go b/cf/commands/config_test.go index e758ddc0681..1b1c3a28d8a 100644 --- a/cf/commands/config_test.go +++ b/cf/commands/config_test.go @@ -57,7 +57,7 @@ var _ = Describe("config command", func() { )) }) - It("fails with usage when a negative timout is passed", func() { + It("fails with usage when a negative timeout is passed", func() { runCommand("--async-timeout", "-555") Expect(ui.Outputs()).To(ContainSubstrings( []string{"Incorrect Usage"}, diff --git a/cf/commands/curl_test.go b/cf/commands/curl_test.go index 1f62002b1ef..7d046d21f89 100644 --- a/cf/commands/curl_test.go +++ b/cf/commands/curl_test.go @@ -148,7 +148,7 @@ var _ = Describe("curl command", func() { Expect(ui.Outputs()).To(ContainSubstrings([]string{"The requested URL returned error: 500"})) }) - It("does not fail on HTTP erros if --fail is false", func() { + It("does not fail on HTTP errors if --fail is false", func() { runCurlWithInputs([]string{"--fail", "false", "/foo"}) Expect(curlRepo.FailOnHTTPError).To(Equal(false)) diff --git a/cf/commands/domain/delete_shared_domain_test.go b/cf/commands/domain/delete_shared_domain_test.go index 284406cb954..6b23eedb0ba 100644 --- a/cf/commands/domain/delete_shared_domain_test.go +++ b/cf/commands/domain/delete_shared_domain_test.go @@ -50,7 +50,7 @@ var _ = Describe("delete-shared-domain command", func() { Expect(runCommand("foo.com")).To(BeFalse()) }) - It("fails if an organiztion is not targeted", func() { + It("fails if an organization is not targeted", func() { requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) targetedOrganizationReq := new(requirementsfakes.FakeTargetedOrgRequirement) @@ -86,7 +86,7 @@ var _ = Describe("delete-shared-domain command", func() { }) }) - Context("when logged in and targeted an organiztion", func() { + Context("when logged in and targeted an organization", func() { BeforeEach(func() { requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) requirementsFactory.NewTargetedOrgRequirementReturns(new(requirementsfakes.FakeTargetedOrgRequirement)) diff --git a/cf/commands/login_test.go b/cf/commands/login_test.go index 88993b7e210..bde9d340e28 100644 --- a/cf/commands/login_test.go +++ b/cf/commands/login_test.go @@ -760,7 +760,7 @@ var _ = Describe("Login Command", func() { }) }) - Describe("and the login fails authenticaton", func() { + Describe("and the login fails authentication", func() { BeforeEach(func() { authRepo.AuthenticateReturns(errors.New("Error authenticating.")) diff --git a/cf/commands/organization/create_org_test.go b/cf/commands/organization/create_org_test.go index d07907e6867..a966a80ffe2 100644 --- a/cf/commands/organization/create_org_test.go +++ b/cf/commands/organization/create_org_test.go @@ -187,7 +187,7 @@ var _ = Describe("create-org command", func() { }) }) - Context("when allowing a non-defualt quota", func() { + Context("when allowing a non-default quota", func() { var ( quota models.QuotaFields ) diff --git a/cf/commands/plugin/install_plugin_test.go b/cf/commands/plugin/install_plugin_test.go index b0b3539a328..c091716e8a8 100644 --- a/cf/commands/plugin/install_plugin_test.go +++ b/cf/commands/plugin/install_plugin_test.go @@ -111,7 +111,7 @@ var _ = Describe("Install", func() { }) runCommand := func(args ...string) bool { - // run command has races becuase it writes and erases temporary files, so the test runner should + // run command has races because it writes and erases temporary files, so the test runner should // really only run one of these at a time. Often the files are actual compiled exes in the test // fixtures path, so it's not easy to prevent the tests from sharing file handles runCmdMutex.Lock() diff --git a/cf/commands/pluginrepo/add_plugin_repo_test.go b/cf/commands/pluginrepo/add_plugin_repo_test.go index 2fd73ad6bc8..81cc767e637 100644 --- a/cf/commands/pluginrepo/add_plugin_repo_test.go +++ b/cf/commands/pluginrepo/add_plugin_repo_test.go @@ -191,7 +191,7 @@ var _ = Describe("add-plugin-repo", func() { Context("When connection could not be established", func() { It("prints a tip", func() { - callAddPluginRepo([]string{"repo", "https://broccoli.nonexistanttld:"}) + callAddPluginRepo([]string{"repo", "https://broccoli.nonexistenttld:"}) Expect(ui.Outputs()).To(ContainSubstrings( []string{"TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."}, diff --git a/cf/commands/pluginrepo/remove_plugin_repo_test.go b/cf/commands/pluginrepo/remove_plugin_repo_test.go index 7f181d2849f..a1241d3ff26 100644 --- a/cf/commands/pluginrepo/remove_plugin_repo_test.go +++ b/cf/commands/pluginrepo/remove_plugin_repo_test.go @@ -15,7 +15,7 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("delte-plugin-repo", func() { +var _ = Describe("delete-plugin-repo", func() { var ( ui *testterm.FakeUI config coreconfig.Repository diff --git a/cf/commands/quota/delete_quota_test.go b/cf/commands/quota/delete_quota_test.go index 15d7712d8c6..f3fb67788a3 100644 --- a/cf/commands/quota/delete_quota_test.go +++ b/cf/commands/quota/delete_quota_test.go @@ -114,19 +114,19 @@ var _ = Describe("delete-quota command", func() { Context("when finding the quota fails", func() { Context("when the quota provided does not exist", func() { BeforeEach(func() { - quotaRepo.FindByNameReturns(models.QuotaFields{}, errors.NewModelNotFoundError("Quota", "non-existent-quota")) + quotaRepo.FindByNameReturns(models.QuotaFields{}, errors.NewModelNotFoundError("Quota", "nonexistent-quota")) }) It("warns the user when that the quota does not exist", func() { - runCommand("-f", "non-existent-quota") + runCommand("-f", "nonexistent-quota") Expect(ui.Outputs()).To(ContainSubstrings( - []string{"Deleting", "non-existent-quota"}, + []string{"Deleting", "nonexistent-quota"}, []string{"OK"}, )) Expect(ui.WarnOutputs).To(ContainSubstrings( - []string{"non-existent-quota", "does not exist"}, + []string{"nonexistent-quota", "does not exist"}, )) }) }) diff --git a/cf/commands/securitygroup/create_security_group_test.go b/cf/commands/securitygroup/create_security_group_test.go index 38e7b62a920..92bd672b3a2 100644 --- a/cf/commands/securitygroup/create_security_group_test.go +++ b/cf/commands/securitygroup/create_security_group_test.go @@ -141,7 +141,7 @@ var _ = Describe("create-security-group command", func() { Expect(ui.Outputs()).To(ContainSubstrings( []string{"FAILED"}, []string{"Incorrect json format: file:", tempFile.Name()}, - []string{"Valid json file exampl"}, + []string{"Valid json file example"}, )) }) }) diff --git a/cf/commands/service/create_service_test.go b/cf/commands/service/create_service_test.go index cc620d91a04..43b3e41798f 100644 --- a/cf/commands/service/create_service_test.go +++ b/cf/commands/service/create_service_test.go @@ -109,7 +109,7 @@ var _ = Describe("create-service command", func() { }) Context("when passing in tags", func() { - It("sucessfully creates a service and passes the tags as json", func() { + It("successfully creates a service and passes the tags as json", func() { callCreateService([]string{"cleardb", "spark", "my-cleardb-service", "-t", "tag1, tag2,tag3, tag4"}) Expect(ui.Outputs()).To(ContainSubstrings( @@ -252,7 +252,7 @@ var _ = Describe("create-service command", func() { []string{"Creating service instance", "my-free-cleardb-service", "my-org", "my-space", "my-user"}, []string{"OK"}, )) - Expect(ui.Outputs()).NotTo(ContainSubstrings([]string{"will incurr a cost"})) + Expect(ui.Outputs()).NotTo(ContainSubstrings([]string{"will incur a cost"})) }) It("warns the user when the service is not free", func() { diff --git a/cf/commands/service/create_user_provided_service_test.go b/cf/commands/service/create_user_provided_service_test.go index 60831dbfa0d..4095c0d0353 100644 --- a/cf/commands/service/create_user_provided_service_test.go +++ b/cf/commands/service/create_user_provided_service_test.go @@ -277,7 +277,7 @@ var _ = Describe("CreateUserProvidedService", func() { flagContext.Parse("service-instance", "-t", "tag1, tag2, tag3, tag4") }) - It("sucessfully creates a service instance and passes the tags as json", func() { + It("successfully creates a service instance and passes the tags as json", func() { Expect(runCLIErr).NotTo(HaveOccurred()) Expect(serviceInstanceRepo.CreateCallCount()).To(Equal(1)) _, _, _, _, tags := serviceInstanceRepo.CreateArgsForCall(0) diff --git a/cf/commands/service/unbind_service_test.go b/cf/commands/service/unbind_service_test.go index f90c2910cbb..e8d479d2dde 100644 --- a/cf/commands/service/unbind_service_test.go +++ b/cf/commands/service/unbind_service_test.go @@ -99,7 +99,7 @@ var _ = Describe("unbind-service command", func() { serviceBindingRepo.DeleteReturns(false, nil) }) - It("warns the user the the service instance does not exist", func() { + It("warns the user the service instance does not exist", func() { callUnbindService([]string{"my-app", "my-service"}) Expect(ui.Outputs()).To(ContainSubstrings( diff --git a/cf/commands/service/update_user_provided_service_test.go b/cf/commands/service/update_user_provided_service_test.go index 962f397e0a9..d46c385c52a 100644 --- a/cf/commands/service/update_user_provided_service_test.go +++ b/cf/commands/service/update_user_provided_service_test.go @@ -275,7 +275,7 @@ var _ = Describe("UpdateUserProvidedService", func() { flagContext.Parse("service-instance", "-t", "tag1, tag2, tag3, tag4") }) - It("sucessfully updates the service instance and passes the tags as json", func() { + It("successfully updates the service instance and passes the tags as json", func() { Expect(runCLIErr).NotTo(HaveOccurred()) Expect(serviceInstanceRepo.UpdateCallCount()).To(Equal(1)) serviceInstanceFields := serviceInstanceRepo.UpdateArgsForCall(0) diff --git a/cf/commands/serviceaccess/disable_service_access_test.go b/cf/commands/serviceaccess/disable_service_access_test.go index e9d7fc026c4..c648781dea3 100644 --- a/cf/commands/serviceaccess/disable_service_access_test.go +++ b/cf/commands/serviceaccess/disable_service_access_test.go @@ -60,7 +60,7 @@ var _ = Describe("disable-service-access command", func() { Expect(runCommand([]string{"foo"})).To(BeFalse()) }) - It("fails with usage when it does not recieve any arguments", func() { + It("fails with usage when it does not receive any arguments", func() { requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) runCommand(nil) Expect(ui.Outputs()).To(ContainSubstrings( diff --git a/cf/commands/serviceaccess/enable_service_access_test.go b/cf/commands/serviceaccess/enable_service_access_test.go index a0fbdeb0334..298687999b3 100644 --- a/cf/commands/serviceaccess/enable_service_access_test.go +++ b/cf/commands/serviceaccess/enable_service_access_test.go @@ -59,7 +59,7 @@ var _ = Describe("enable-service-access command", func() { Expect(runCommand([]string{"foo"})).To(BeFalse()) }) - It("fails with usage when it does not recieve any arguments", func() { + It("fails with usage when it does not receive any arguments", func() { requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) runCommand(nil) Expect(ui.Outputs()).To(ContainSubstrings( diff --git a/cf/commands/spacequota/create_space_quota_test.go b/cf/commands/spacequota/create_space_quota_test.go index 50ae6dcb3e1..112d85d3e4d 100644 --- a/cf/commands/spacequota/create_space_quota_test.go +++ b/cf/commands/spacequota/create_space_quota_test.go @@ -143,7 +143,7 @@ var _ = Describe("create-space-quota", func() { )) }) - It("sets the instance memory limit to unlimiited", func() { + It("sets the instance memory limit to unlimited", func() { Expect(quotaRepo.CreateArgsForCall(0).InstanceMemoryLimit).To(Equal(int64(-1))) }) @@ -272,7 +272,7 @@ var _ = Describe("create-space-quota", func() { cmd.SetDependency(deps, false) }) - It("alets the user when creating the quota fails", func() { + It("alerts the user when creating the quota fails", func() { Expect(runCLIErr).To(HaveOccurred()) Expect(ui.Outputs()).To(ContainSubstrings( []string{"Creating space quota", "my-quota", "my-org"}, diff --git a/cf/commands/spacequota/delete_space_quota_test.go b/cf/commands/spacequota/delete_space_quota_test.go index db65d8fc42b..b7737ea5483 100644 --- a/cf/commands/spacequota/delete_space_quota_test.go +++ b/cf/commands/spacequota/delete_space_quota_test.go @@ -129,19 +129,19 @@ var _ = Describe("delete-space-quota command", func() { Context("when finding the quota fails", func() { Context("when the quota provided does not exist", func() { BeforeEach(func() { - quotaRepo.FindByNameReturns(models.SpaceQuota{}, errors.NewModelNotFoundError("Quota", "non-existent-quota")) + quotaRepo.FindByNameReturns(models.SpaceQuota{}, errors.NewModelNotFoundError("Quota", "nonexistent-quota")) }) It("warns the user when that the quota does not exist", func() { - runCommand("-f", "non-existent-quota") + runCommand("-f", "nonexistent-quota") Expect(ui.Outputs()).To(ContainSubstrings( - []string{"Deleting", "non-existent-quota"}, + []string{"Deleting", "nonexistent-quota"}, []string{"OK"}, )) Expect(ui.WarnOutputs).To(ContainSubstrings( - []string{"non-existent-quota", "does not exist"}, + []string{"nonexistent-quota", "does not exist"}, )) }) }) diff --git a/cf/commands/user/delete_user_test.go b/cf/commands/user/delete_user_test.go index 1359ec124e7..0677a42aa78 100644 --- a/cf/commands/user/delete_user_test.go +++ b/cf/commands/user/delete_user_test.go @@ -124,7 +124,7 @@ var _ = Describe("delete-user command", func() { }, nil) }) - It("returns a muliple users found error", func() { + It("returns a multiple users found error", func() { runCommand("user-name") Expect(ui.Outputs()).To(ContainSubstrings( diff --git a/cf/commands/user/org_users_test.go b/cf/commands/user/org_users_test.go index 0866fe1952e..7ead8a1c292 100644 --- a/cf/commands/user/org_users_test.go +++ b/cf/commands/user/org_users_test.go @@ -85,7 +85,7 @@ var _ = Describe("org-users command", func() { requirementsFactory.NewOrganizationRequirementReturns(organizationReq) }) - Context("shows friendly messaage when no users in ORG_MANAGER role", func() { + Context("shows friendly message when no users in ORG_MANAGER role", func() { It("shows the special users in the given org", func() { userRepo.ListUsersInOrgForRoleWithNoUAAStub = func(_ string, roleName models.Role) ([]models.UserFields, error) { userFields := map[models.Role][]models.UserFields{ @@ -117,7 +117,7 @@ var _ = Describe("org-users command", func() { }) }) - Context("shows friendly messaage when no users in BILLING_MANAGER role", func() { + Context("shows friendly message when no users in BILLING_MANAGER role", func() { It("shows the special users in the given org", func() { userRepo.ListUsersInOrgForRoleWithNoUAAStub = func(_ string, roleName models.Role) ([]models.UserFields, error) { userFields := map[models.Role][]models.UserFields{ @@ -149,7 +149,7 @@ var _ = Describe("org-users command", func() { }) }) - Context("shows friendly messaage when no users in ORG_AUDITOR role", func() { + Context("shows friendly message when no users in ORG_AUDITOR role", func() { It("shows the special users in the given org", func() { userRepo.ListUsersInOrgForRoleWithNoUAAStub = func(_ string, roleName models.Role) ([]models.UserFields, error) { userFields := map[models.Role][]models.UserFields{ diff --git a/cf/flags/flags_test.go b/cf/flags/flags_test.go index 1b29cd2978e..14acb4efe9d 100644 --- a/cf/flags/flags_test.go +++ b/cf/flags/flags_test.go @@ -8,7 +8,7 @@ import ( var _ = Describe("Flags", func() { Describe("FlagContext", func() { - Describe("Parsing and retriving values", func() { + Describe("Parsing and retrieving values", func() { var ( fCtx flags.FlagContext cmdFlagMap map[string]flags.FlagSet @@ -73,7 +73,7 @@ var _ = Describe("Flags", func() { Ω(fCtx.Bool("skip2")).To(Equal(true), "skip2 should be true") Ω(fCtx.Bool("name")).To(Equal(false), "name should be false") Ω(fCtx.String("name")).To(Equal("johndoe"), "name should be johndoe") - Expect(fCtx.Bool("non-exisit-flag")).To(Equal(false)) + Expect(fCtx.Bool("non-exist-flag")).To(Equal(false)) }) It("sets Bool() to return true if bool flag is provided with invalid value", func() { @@ -90,7 +90,7 @@ var _ = Describe("Flags", func() { Ω(fCtx.Bool("skip")).To(Equal(true), "skip should be true") Ω(fCtx.Bool("name")).To(Equal(false), "name should be false") - Expect(fCtx.Bool("non-exisit-flag")).To(Equal(false)) + Expect(fCtx.Bool("non-exist-flag")).To(Equal(false)) }) It("sets String() to return provided value when a string flag is provided", func() { diff --git a/cf/i18n/README-i18n.md b/cf/i18n/README-i18n.md index 6b4e1d5a9d2..097e568d823 100644 --- a/cf/i18n/README-i18n.md +++ b/cf/i18n/README-i18n.md @@ -8,7 +8,7 @@ If you are interested in submitting translations for a currently unsupported lan ## How can you contribute? -If you see typos, errors in grammar, ambiguous strings or English after setting your locale, please find the appropriate entry in the corresponding `i18n/resources/_.all.json` file and submit a [pull request](https://help.github.com/articles/creating-a-pull-request/) with the fix(es). It is much better to submit small pull requests as you complete translations, rather than submitting a large one. This makes it much easier to rapidly merge your changes in. You can also report translations needing fixes as an issue in Github. +If you see typos, errors in grammar, ambiguous strings or English after setting your locale, please find the appropriate entry in the corresponding `i18n/resources/_.all.json` file and submit a [pull request](https://help.github.com/articles/creating-a-pull-request/) with the fix(es). It is much better to submit small pull requests as you complete translations, rather than submitting a large one. This makes it much easier to rapidly merge your changes in. You can also report translations needing fixes as an issue in GitHub. Pull requests should only contain changes to "translation" values; the "id" value should not change as it is the key which is used to find the translations, and will always be English. For example: diff --git a/cf/ssh/options/ssh_options_test.go b/cf/ssh/options/ssh_options_test.go index b4fe1f8d25d..4f77b1dd700 100644 --- a/cf/ssh/options/ssh_options_test.go +++ b/cf/ssh/options/ssh_options_test.go @@ -121,7 +121,7 @@ var _ = Describe("SSHOptions", func() { BeforeEach(func() { args = append(args, "app-name", "-tt") }) - It("foces tty allocation", func() { + It("forces tty allocation", func() { Expect(opts.TerminalRequest).To(Equal(options.RequestTTYForce)) }) }) @@ -190,7 +190,7 @@ var _ = Describe("SSHOptions", func() { }) }) - Context("with an explit bind address", func() { + Context("with an explicit bind address", func() { BeforeEach(func() { args = append(args, "-L", "explicit:9999:remote:8888") }) diff --git a/cf/ssh/ssh_test.go b/cf/ssh/ssh_test.go index b6ef1094f02..7d16ea674f0 100644 --- a/cf/ssh/ssh_test.go +++ b/cf/ssh/ssh_test.go @@ -481,7 +481,7 @@ var _ = Describe("SSH", func() { Expect(fakeSecureSession.ShellCallCount()).To(Equal(1)) }) - It("does not not restore the terminal", func() { + It("does not restore the terminal", func() { Expect(fakeSecureSession.ShellCallCount()).To(Equal(1)) Expect(fakeTerminalHelper.SetRawTerminalCallCount()).To(Equal(1)) Expect(fakeTerminalHelper.RestoreTerminalCallCount()).To(Equal(0)) @@ -581,7 +581,7 @@ var _ = Describe("SSH", func() { }) }) - Context("when a command is specifed", func() { + Context("when a command is specified", func() { BeforeEach(func() { opts.Command = []string{"echo", "-n", "hello"} }) diff --git a/cf/terminal/table.go b/cf/terminal/table.go index a123e3ff310..debd10d19a5 100644 --- a/cf/terminal/table.go +++ b/cf/terminal/table.go @@ -174,7 +174,7 @@ func (t *Table) calculateMaxSize(transformer rowTransformer, rowIndex int, row [ return nil } -// printRow is responsible for the layouting, transforming and +// printRow is responsible for the laying-out, transforming and // printing of the string in a single row func (t *Table) printRow(result io.Writer, transformer rowTransformer, rowIndex int, row []string) error { @@ -255,7 +255,7 @@ func (t *Table) printRow(result io.Writer, transformer rowTransformer, rowIndex } // printCellValue pads the specified string to the width of the given -// column, adds the spacing bewtween columns, and returns the result. +// column, adds the spacing between columns, and returns the result. func (t *Table) printCellValue(result io.Writer, transformer rowTransformer, col, last int, value string) error { value = trim(transformer.Transform(col, trim(value))) fmt.Fprint(result, value) diff --git a/cf/util/downloader/file_download_test.go b/cf/util/downloader/file_download_test.go index 2665771ddaf..64fb2a4e6d9 100644 --- a/cf/util/downloader/file_download_test.go +++ b/cf/util/downloader/file_download_test.go @@ -154,7 +154,7 @@ var _ = Describe("Downloader", func() { Context("when the URL is invalid", func() { It("returns an error", func() { - _, _, err := d.DownloadFile("http://going.nowwhere/abc.zip") + _, _, err := d.DownloadFile("http://going.nowhere/abc.zip") Expect(err).To(HaveOccurred()) }) }) diff --git a/cf/util/testhelpers/io/io.go b/cf/util/testhelpers/io/io.go index df710d856f8..571232151d4 100644 --- a/cf/util/testhelpers/io/io.go +++ b/cf/util/testhelpers/io/io.go @@ -50,7 +50,7 @@ func CaptureOutput(block func()) []string { doneWriting := make(chan bool) result := make(chan []string) - go captureOutputAsyncronously(doneWriting, result, r) + go captureOutputAsynchronously(doneWriting, result, r) block() w.Close() @@ -64,7 +64,7 @@ func CaptureOutput(block func()) []string { (looking at you, Windows). To counteract this, we need to read in a goroutine from one end of the pipe and return the result across a channel. */ -func captureOutputAsyncronously(doneWriting <-chan bool, result chan<- []string, reader io.Reader) { +func captureOutputAsynchronously(doneWriting <-chan bool, result chan<- []string, reader io.Reader) { var readingString string for { diff --git a/cf/util/testhelpers/matchers/match_func_name_test.go b/cf/util/testhelpers/matchers/match_func_name_test.go index 604b740d90b..c5b6e942c87 100644 --- a/cf/util/testhelpers/matchers/match_func_name_test.go +++ b/cf/util/testhelpers/matchers/match_func_name_test.go @@ -99,7 +99,7 @@ var _ = Describe("MatchChangeAppFuncsByName", func() { }) }) - Describe("NegatedFaileureMessage", func() { + Describe("NegatedFailureMessage", func() { It("shows expected and actual", func() { matcher = MatchFuncsByName(dummyFunc) matcher.Match([]func(){dummyNotMatchFunc}) diff --git a/cf/util/testhelpers/net/server.go b/cf/util/testhelpers/net/server.go index 79be4b70c83..09ce8e811c3 100644 --- a/cf/util/testhelpers/net/server.go +++ b/cf/util/testhelpers/net/server.go @@ -41,7 +41,7 @@ func urlQueryContains(container, containee url.Values) bool { //Example: "foo:bar;baz:qux" is semantically the same as "baz:qux;foo:bar". CC doesn't care about order. - //Therefore, we crack apart "q" params on their seperator (a colon) and compare the resulting + //Therefore, we crack apart "q" params on their separator (a colon) and compare the resulting //substrings. No other params seem to use semicolon separators AND are order-dependent, so we just //run all params through the same process. for key := range containee { diff --git a/command/common/install_plugin_command.go b/command/common/install_plugin_command.go index e38fa3864e2..16a51682fad 100644 --- a/command/common/install_plugin_command.go +++ b/command/common/install_plugin_command.go @@ -407,7 +407,7 @@ func (cmd InstallPluginCommand) installPluginPrompt(template string, templateVal } if !really { - log.Debug("plugin confirmation - 'no' inputed") + log.Debug("plugin confirmation - 'no' inputted") return cancelInstall{} } diff --git a/command/common/install_plugin_from_repo_command_test.go b/command/common/install_plugin_from_repo_command_test.go index a92a4de9f9e..11fe6bd9e80 100644 --- a/command/common/install_plugin_from_repo_command_test.go +++ b/command/common/install_plugin_from_repo_command_test.go @@ -307,7 +307,7 @@ var _ = Describe("install-plugin command", func() { }) }) - When("creating an exectuable copy succeeds", func() { + When("creating an executable copy succeeds", func() { BeforeEach(func() { fakeActor.CreateExecutableCopyReturns("copy-path", nil) }) diff --git a/command/v7/add_network_policy_command_test.go b/command/v7/add_network_policy_command_test.go index 998afb510c9..90e9a0160de 100644 --- a/command/v7/add_network_policy_command_test.go +++ b/command/v7/add_network_policy_command_test.go @@ -115,7 +115,7 @@ var _ = Describe("add-network-policy Command", func() { }) }) - When("both protocol and port are specificed", func() { + When("both protocol and port are specified", func() { BeforeEach(func() { cmd.Protocol = flag.NetworkProtocol{Protocol: protocol} cmd.Port = flag.NetworkPort{StartPort: 8080, EndPort: 8081} diff --git a/command/v7/auth_command.go b/command/v7/auth_command.go index 6b787b3245f..0d5f70c4702 100644 --- a/command/v7/auth_command.go +++ b/command/v7/auth_command.go @@ -18,7 +18,7 @@ type AuthCommand struct { RequiredArgs flag.Authentication `positional-args:"yes"` ClientCredentials bool `long:"client-credentials" description:"Use (non-user) service account (also called client credentials)"` Origin string `long:"origin" description:"Indicates the identity provider to be used for authentication"` - usage interface{} `usage:"CF_NAME auth USERNAME PASSWORD\n CF_NAME auth USERNAME PASSWORD --origin ORIGIN\n CF_NAME auth CLIENT_ID CLIENT_SECRET --client-credentials\n\nENVIRONMENT VARIABLES:\n CF_USERNAME=user Authenticating user. Overridden if USERNAME argument is provided.\n CF_PASSWORD=password Password associated with user. Overriden if PASSWORD argument is provided.\n\nWARNING:\n Providing your password as a command line option is highly discouraged\n Your password may be visible to others and may be recorded in your shell history\n Consider using the CF_PASSWORD environment variable instead\n\nEXAMPLES:\n CF_NAME auth name@example.com \"my password\" (use quotes for passwords with a space)\n CF_NAME auth name@example.com \"\\\"password\\\"\" (escape quotes if used in password)"` + usage interface{} `usage:"CF_NAME auth USERNAME PASSWORD\n CF_NAME auth USERNAME PASSWORD --origin ORIGIN\n CF_NAME auth CLIENT_ID CLIENT_SECRET --client-credentials\n\nENVIRONMENT VARIABLES:\n CF_USERNAME=user Authenticating user. Overridden if USERNAME argument is provided.\n CF_PASSWORD=password Password associated with user. Overridden if PASSWORD argument is provided.\n\nWARNING:\n Providing your password as a command line option is highly discouraged\n Your password may be visible to others and may be recorded in your shell history\n Consider using the CF_PASSWORD environment variable instead\n\nEXAMPLES:\n CF_NAME auth name@example.com \"my password\" (use quotes for passwords with a space)\n CF_NAME auth name@example.com \"\\\"password\\\"\" (escape quotes if used in password)"` relatedCommands interface{} `related_commands:"api, login, target"` } diff --git a/command/v7/buildpacks_command_test.go b/command/v7/buildpacks_command_test.go index 6dd304e0686..abcf60f2b73 100644 --- a/command/v7/buildpacks_command_test.go +++ b/command/v7/buildpacks_command_test.go @@ -75,7 +75,7 @@ var _ = Describe("buildpacks Command", func() { fakeActor.GetCurrentUserReturns(configv3.User{Name: "apple"}, nil) }) - It("should print text indicating its runnning", func() { + It("should print text indicating its running", func() { Expect(executeErr).NotTo(HaveOccurred()) Expect(testUI.Out).To(Say(`Getting buildpacks as apple\.\.\.`)) }) diff --git a/command/v7/create_app_manifest_command_test.go b/command/v7/create_app_manifest_command_test.go index 23894166f13..6f132cafdc8 100644 --- a/command/v7/create_app_manifest_command_test.go +++ b/command/v7/create_app_manifest_command_test.go @@ -116,7 +116,7 @@ var _ = Describe("create-app-manifest Command", func() { Expect(os.RemoveAll(tempDir)).ToNot(HaveOccurred()) }) - It("creates application manifest in current directry as -manifest.yml", func() { + It("creates application manifest in current directory as -manifest.yml", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(fakeActor.GetRawApplicationManifestByNameAndSpaceCallCount()).To(Equal(1)) diff --git a/command/v7/curl_command.go b/command/v7/curl_command.go index bddc3cc421b..8f8d3be40d3 100644 --- a/command/v7/curl_command.go +++ b/command/v7/curl_command.go @@ -11,14 +11,14 @@ import ( type CurlCommand struct { BaseCommand - RequiredArgs flag.APIPath `positional-args:"yes"` - CustomHeaders []string `short:"H" description:"Custom headers to include in the request, flag can be specified multiple times"` - HTTPMethod string `short:"X" description:"HTTP method (GET,POST,PUT,DELETE,etc)"` - HTTPData flag.PathWithAt `short:"d" description:"HTTP data to include in the request body, or '@' followed by a file name to read the data from"` - FailOnHTTPError bool `short:"f" long:"fail" description:"Server errors return exit code 22"` - IncludeReponseHeaders bool `short:"i" description:"Include response headers in the output"` - OutputFile flag.Path `long:"output" description:"Write curl body to FILE instead of stdout"` - usage interface{} `usage:"CF_NAME curl PATH [-iv] [-X METHOD] [-H HEADER]... [-d DATA] [--output FILE]\n\n By default 'CF_NAME curl' will perform a GET to the specified PATH. If data\n is provided via -d, a POST will be performed instead, and the Content-Type\n will be set to application/json. You may override headers with -H and the\n request method with -X.\n\n For API documentation, please visit http://apidocs.cloudfoundry.org.\n\nEXAMPLES:\n CF_NAME curl \"/v2/apps\" -X GET -H \"Content-Type: application/x-www-form-urlencoded\" -d 'q=name:myapp'\n CF_NAME curl \"/v2/apps\" -d @/path/to/file"` + RequiredArgs flag.APIPath `positional-args:"yes"` + CustomHeaders []string `short:"H" description:"Custom headers to include in the request, flag can be specified multiple times"` + HTTPMethod string `short:"X" description:"HTTP method (GET,POST,PUT,DELETE,etc)"` + HTTPData flag.PathWithAt `short:"d" description:"HTTP data to include in the request body, or '@' followed by a file name to read the data from"` + FailOnHTTPError bool `short:"f" long:"fail" description:"Server errors return exit code 22"` + IncludeResponseHeaders bool `short:"i" description:"Include response headers in the output"` + OutputFile flag.Path `long:"output" description:"Write curl body to FILE instead of stdout"` + usage interface{} `usage:"CF_NAME curl PATH [-iv] [-X METHOD] [-H HEADER]... [-d DATA] [--output FILE]\n\n By default 'CF_NAME curl' will perform a GET to the specified PATH. If data\n is provided via -d, a POST will be performed instead, and the Content-Type\n will be set to application/json. You may override headers with -H and the\n request method with -X.\n\n For API documentation, please visit http://apidocs.cloudfoundry.org.\n\nEXAMPLES:\n CF_NAME curl \"/v2/apps\" -X GET -H \"Content-Type: application/x-www-form-urlencoded\" -d 'q=name:myapp'\n CF_NAME curl \"/v2/apps\" -d @/path/to/file"` } func (cmd CurlCommand) Execute(args []string) error { @@ -40,7 +40,7 @@ func (cmd CurlCommand) Execute(args []string) error { var bytesToWrite []byte - if cmd.IncludeReponseHeaders { + if cmd.IncludeResponseHeaders { headerBytes, _ := httputil.DumpResponse(httpResponse, false) bytesToWrite = append(bytesToWrite, headerBytes...) } diff --git a/command/v7/curl_command_test.go b/command/v7/curl_command_test.go index c76d76bc55a..440a5dc343c 100644 --- a/command/v7/curl_command_test.go +++ b/command/v7/curl_command_test.go @@ -26,13 +26,13 @@ var _ = Describe("curl Command", func() { fakeActor *v7fakes.FakeActor binaryName string - CustomHeaders []string - HTTPMethod string - HTTPData flag.PathWithAt - FailOnHTTPError bool - IncludeReponseHeaders bool - OutputFile flag.Path - executeErr error + CustomHeaders []string + HTTPMethod string + HTTPData flag.PathWithAt + FailOnHTTPError bool + IncludeResponseHeaders bool + OutputFile flag.Path + executeErr error ) BeforeEach(func() { @@ -49,13 +49,13 @@ var _ = Describe("curl Command", func() { SharedActor: fakeSharedActor, Actor: fakeActor, }, - RequiredArgs: flag.APIPath{Path: "/"}, - CustomHeaders: CustomHeaders, - HTTPMethod: HTTPMethod, - HTTPData: HTTPData, - FailOnHTTPError: FailOnHTTPError, - IncludeReponseHeaders: IncludeReponseHeaders, - OutputFile: OutputFile, + RequiredArgs: flag.APIPath{Path: "/"}, + CustomHeaders: CustomHeaders, + HTTPMethod: HTTPMethod, + HTTPData: HTTPData, + FailOnHTTPError: FailOnHTTPError, + IncludeResponseHeaders: IncludeResponseHeaders, + OutputFile: OutputFile, } binaryName = "faceman" @@ -128,7 +128,7 @@ var _ = Describe("curl Command", func() { When("the include-response-headers flag is set", func() { BeforeEach(func() { - cmd.IncludeReponseHeaders = true + cmd.IncludeResponseHeaders = true }) It("includes the headers in the output", func() { diff --git a/command/v7/delete_command_test.go b/command/v7/delete_command_test.go index 880f2629aa3..3118c9de29e 100644 --- a/command/v7/delete_command_test.go +++ b/command/v7/delete_command_test.go @@ -227,7 +227,7 @@ var _ = Describe("delete Command", func() { fakeActor.DeleteApplicationByNameAndSpaceReturns(v7action.Warnings{"some-warning"}, errors.New("some-error")) }) - It("displays all warnings, and returns the erorr", func() { + It("displays all warnings, and returns the error", func() { Expect(testUI.Err).To(Say("some-warning")) Expect(testUI.Out).To(Say(`Deleting app some-app in org some-org / space some-space as steve\.\.\.`)) Expect(testUI.Out).ToNot(Say("OK")) diff --git a/command/v7/delete_shared_domain_command_test.go b/command/v7/delete_shared_domain_command_test.go index 11bbfc3e846..a8fc4cd8eb9 100644 --- a/command/v7/delete_shared_domain_command_test.go +++ b/command/v7/delete_shared_domain_command_test.go @@ -212,7 +212,7 @@ var _ = Describe("delete-shared-domain Command", func() { fakeActor.DeleteDomainReturns(v7action.Warnings{"some-warning"}, errors.New("some-error")) }) - It("displays all warnings, and returns the erorr", func() { + It("displays all warnings, and returns the error", func() { Expect(testUI.Err).To(Say("some-warning")) Expect(testUI.Out).To(Say(`Deleting domain some-domain.com as steve\.\.\.`)) Expect(testUI.Out).ToNot(Say("OK")) diff --git a/command/v7/disable_feature_flag_command_test.go b/command/v7/disable_feature_flag_command_test.go index a5ec2ab340c..bf8c2f10137 100644 --- a/command/v7/disable_feature_flag_command_test.go +++ b/command/v7/disable_feature_flag_command_test.go @@ -96,7 +96,7 @@ var _ = Describe("Disable Feature Flag Command", func() { fakeActor.DisableFeatureFlagReturns(v7action.Warnings{"this is a warning"}, nil) }) - It("diaplays the feature flag was enabled", func() { + It("displays the feature flag was enabled", func() { featureFlagArgs := fakeActor.DisableFeatureFlagArgsForCall(0) Expect(featureFlagArgs).To(Equal(featureFlagName)) Expect(executeErr).NotTo(HaveOccurred()) diff --git a/command/v7/enable_feature_flag_command_test.go b/command/v7/enable_feature_flag_command_test.go index 84717d362a4..ae1a14f9676 100644 --- a/command/v7/enable_feature_flag_command_test.go +++ b/command/v7/enable_feature_flag_command_test.go @@ -96,7 +96,7 @@ var _ = Describe("Enable Feature Flag Command", func() { fakeActor.EnableFeatureFlagReturns(v7action.Warnings{"this is a warning"}, nil) }) - It("diaplays the feature flag was enabled", func() { + It("displays the feature flag was enabled", func() { featureFlagArgs := fakeActor.EnableFeatureFlagArgsForCall(0) Expect(featureFlagArgs).To(Equal(featureFlagName)) Expect(executeErr).NotTo(HaveOccurred()) diff --git a/command/v7/login_command_test.go b/command/v7/login_command_test.go index b2bcb502230..e49ffb52e3d 100644 --- a/command/v7/login_command_test.go +++ b/command/v7/login_command_test.go @@ -1380,7 +1380,7 @@ var _ = Describe("login Command", func() { fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space"}) }) - It("displays that the spacce has been targeted", func() { + It("displays that the space has been targeted", func() { Expect(testUI.Out).To(Say(`space:\s+some-space`)) }) }) diff --git a/command/v7/shared/new_clients_test.go b/command/v7/shared/new_clients_test.go index 04bb1ec4b87..75a4049f7a7 100644 --- a/command/v7/shared/new_clients_test.go +++ b/command/v7/shared/new_clients_test.go @@ -57,7 +57,7 @@ var _ = Describe("New Clients", func() { }) }) - When("not targetting", func() { + When("not targeting", func() { It("does not target", func() { ccClient := NewWrappedCloudControllerClient(fakeConfig, testUI) Expect(ccClient).ToNot(BeNil()) diff --git a/command/v7/ssh_command_test.go b/command/v7/ssh_command_test.go index 492e0158220..25721f6c37c 100644 --- a/command/v7/ssh_command_test.go +++ b/command/v7/ssh_command_test.go @@ -203,14 +203,14 @@ var _ = Describe("ssh Command", func() { Entry("default - auto TTY", false, false, false, nil, sharedaction.RequestTTYAuto), Entry("disable tty - no TTY", true, false, false, nil, sharedaction.RequestTTYNo), Entry("force tty - forced TTY", false, true, false, nil, sharedaction.RequestTTYForce), - Entry("psudo tty - yes TTY", false, false, true, nil, sharedaction.RequestTTYYes), + Entry("pseudo tty - yes TTY", false, false, true, nil, sharedaction.RequestTTYYes), Entry("disable and force tty", true, true, false, translatableerror.ArgumentCombinationError{Args: []string{ "--disable-pseudo-tty", "-T", "--force-pseudo-tty", "--request-pseudo-tty", "-t", }}, sharedaction.TTYOption(0), ), - Entry("disable and requst tty", true, false, true, + Entry("disable and request tty", true, false, true, translatableerror.ArgumentCombinationError{Args: []string{ "--disable-pseudo-tty", "-T", "--force-pseudo-tty", "--request-pseudo-tty", "-t", }}, diff --git a/doc/adr/0004-v7-push-refactor.md b/doc/adr/0004-v7-push-refactor.md index 3fd517cc447..29cbe7b69bc 100644 --- a/doc/adr/0004-v7-push-refactor.md +++ b/doc/adr/0004-v7-push-refactor.md @@ -127,11 +127,11 @@ for _, changeAppFunc := range actor.ChangeApplicationSequence(plan) { } ``` -This is quite similar to the loop through `actor.PreparePushPlanSequence` above. We loop through `actor.ChangeApplicationSequence(plan)`, which returns an array of functions, and we call each one with the push plan. Each one returns a push plan that then gets passed in to the next function. +This is quite similar to the loop through `actor.PreparePushPlanSequence` above. We loop through `actor.ChangeApplicationSequence(plan)`, which returns an array of functions, and we call each one with the push plan. Each one returns a push plan that then gets passed into the next function. _Note: The rest of that code uses streams to report progress, errors, and warnings, but this is not the focus of this ADR (and we may end up changing this as well)._ -The **biggest difference** from `Conceputalize` is that instead of being a static list of functions (like `actor.PreparePushPlanSequence`), `actor.ChangeApplicationSequence` is a function that takes in a push plan and returns an array of `ChangeApplicationFunc`s. This allows us to dynamically build up the sequence of actions we run based on the push plan, rather than run the same sequence every time. +The **biggest difference** from `Conceptualize` is that instead of being a static list of functions (like `actor.PreparePushPlanSequence`), `actor.ChangeApplicationSequence` is a function that takes in a push plan and returns an array of `ChangeApplicationFunc`s. This allows us to dynamically build up the sequence of actions we run based on the push plan, rather than run the same sequence every time. Let's look at how that works: diff --git a/doc/adr/0007-integration-vs-command-test.md b/doc/adr/0007-integration-vs-command-test.md index 8d7a721741d..6de1052d9c3 100644 --- a/doc/adr/0007-integration-vs-command-test.md +++ b/doc/adr/0007-integration-vs-command-test.md @@ -121,7 +121,7 @@ When("an error is encountered getting the provided security group", func() { ## Consequences - Since this is not a very big change, we are not expecting to lose any - converage or increase run times. This is mostly for organization + coverage or increase run times. This is mostly for organization purposes. - There are no plans to retrofit existing tests to adhere to this ADR. diff --git a/doc/adr/0012-de-version-v8-code-base.md b/doc/adr/0012-de-version-v8-code-base.md index b27e23f9ab5..a3d725914ff 100644 --- a/doc/adr/0012-de-version-v8-code-base.md +++ b/doc/adr/0012-de-version-v8-code-base.md @@ -22,7 +22,7 @@ After [PR #2180](https://github.com/cloudfoundry/cli/pull/2180) we should be in * integration/v7 => integration/tests * Makefile ivi (integration versioned isolated) command => change to ii (integration isolated) * Makefile integration versioned global (ivg) => see above -* cf/ legacy directory => maybe rename it to something like legacy-cf, and put a README descring +* cf/ legacy directory => maybe rename it to something like legacy-cf, and put a README describing ## Decision diff --git a/fixtures/plugins/test_1.go b/fixtures/plugins/test_1.go index 3419bc7f91d..2d57bf235c5 100644 --- a/fixtures/plugins/test_1.go +++ b/fixtures/plugins/test_1.go @@ -95,7 +95,7 @@ func (c *Test1) GetMetadata() plugin.PluginMetadata { Alias: "test_1_cmd1_alias", HelpText: "help text for test_1_cmd1", UsageDetails: plugin.Usage{ - Usage: "Test plugin command\n cf test_1_cmd1 [-a] [-b] [--no-ouput]", + Usage: "Test plugin command\n cf test_1_cmd1 [-a] [-b] [--no-output]", Options: map[string]string{ "a": "flag to do nothing", "b": "another flag to do nothing", diff --git a/i18n/resources/i18n_resources.go b/i18n/resources/i18n_resources.go index 3ba2b2f077a..ac21975801a 100644 --- a/i18n/resources/i18n_resources.go +++ b/i18n/resources/i18n_resources.go @@ -281,8 +281,8 @@ func resourcesZhHantAllJson() (*asset, error) { // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) @@ -307,8 +307,8 @@ func MustAsset(name string) []byte { // It returns an error if the asset could not be found or // could not be loaded. func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) @@ -357,8 +357,8 @@ var _bindata = map[string]func() (*asset, error){ func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") + canonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(canonicalName, "/") for _, p := range pathList { node = node.Children[p] if node == nil { @@ -439,6 +439,6 @@ func RestoreAssets(dir, name string) error { } func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) + canonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) } diff --git a/integration/helpers/login.go b/integration/helpers/login.go index 07e28f81d2f..74445b19fbf 100644 --- a/integration/helpers/login.go +++ b/integration/helpers/login.go @@ -54,7 +54,7 @@ func GetAPI() string { return apiURL } -// LoginAs logs in to the CLI with 'cf auth' and the given username and password, +// LoginAs logs into the CLI with 'cf auth' and the given username and password, // retrying up to 3 times on failures. func LoginAs(username, password string) { env := map[string]string{ @@ -75,7 +75,7 @@ func LoginAs(username, password string) { Expect(session.ExitCode()).To(Equal(0)) } -// LoginCF logs in to the CLI using the username and password from the CF_INT_USERNAME +// LoginCF logs into the CLI using the username and password from the CF_INT_USERNAME // and CF_INT_PASSWORD environment variables, respectively, defaulting to "admin" for // each if either is not set. func LoginCF() string { @@ -87,7 +87,7 @@ func LoginCF() string { return username } -// LoginCFWithClientCredentials logs in to the CLI using client credentials from the CF_INT_CLIENT_ID and +// LoginCFWithClientCredentials logs into the CLI using client credentials from the CF_INT_CLIENT_ID and // CF_INT_CLIENT_SECRET environment variables and returns the client ID. If these environment variables // are not set, it skips the current test. func LoginCFWithClientCredentials() string { @@ -146,13 +146,13 @@ func TargetOrg(org string) { Eventually(CF("target", "-o", org)).Should(Exit(0)) } -// ClearTarget logs out and logs back in to the CLI using LogoutCF and LoginCF. +// ClearTarget logs out and logs back into the CLI using LogoutCF and LoginCF. func ClearTarget() { LogoutCF() LoginCF() } -// SetupCF logs in to the CLI with LoginCF, creates the given org and space, and targets that +// SetupCF logs into the CLI with LoginCF, creates the given org and space, and targets that // org and space. func SetupCF(org string, space string) { LoginCF() @@ -160,14 +160,14 @@ func SetupCF(org string, space string) { TargetOrgAndSpace(org, space) } -// SetupCFWithOrgOnly logs in to the CLI with LoginCF, creates the given org, and targets it. +// SetupCFWithOrgOnly logs into the CLI with LoginCF, creates the given org, and targets it. func SetupCFWithOrgOnly(org string) { LoginCF() CreateOrg(org) TargetOrg(org) } -// SetupCFWithGeneratedOrgAndSpaceNames logs in to the CLI with LoginCF, creates the org and +// SetupCFWithGeneratedOrgAndSpaceNames logs into the CLI with LoginCF, creates the org and // space with generated names, and targets that org and space. Returns the generated org so // that it can be deleted easily in cleanup step of the test. func SetupCFWithGeneratedOrgAndSpaceNames() string { diff --git a/integration/helpers/version.go b/integration/helpers/version.go index bf9dd585711..d5418d069b0 100644 --- a/integration/helpers/version.go +++ b/integration/helpers/version.go @@ -79,7 +79,7 @@ func SkipIfUAAVersionLessThan(version string) { } } -// SkipIfUAAVersionAtLeast is used to skip tests if the UAA varsion >= the specified version. +// SkipIfUAAVersionAtLeast is used to skip tests if the UAA version >= the specified version. func SkipIfUAAVersionAtLeast(version string) { if IsUAAVersionAtLeast(version) { Skip(fmt.Sprintf("Test requires UAA version less than %s", version)) @@ -96,10 +96,10 @@ func matchMajorAPIVersion(minVersion string) string { // GetAPIVersionV2 returns the V2 api version of the targeted API func GetAPIVersionV2() string { - return fetchAPIVersion().Links.CloudContollerV2.Meta.Version + return fetchAPIVersion().Links.CloudControllerV2.Meta.Version } -// SkipIfVersionLessThan is used to skip tests if the the API version < the specified version. If +// SkipIfVersionLessThan is used to skip tests if the API version < the specified version. If // minVersion contains the prefix 3 then the v3 version is checked, otherwise the v2 version is used. func SkipIfVersionLessThan(minVersion string) { if ignoreAPIVersion() { @@ -112,7 +112,7 @@ func SkipIfVersionLessThan(minVersion string) { } } -// SkipIfVersionLessThan is used to skip tests if the the API version >= the specified version. If +// SkipIfVersionLessThan is used to skip tests if the API version >= the specified version. If // maxVersion contains the prefix 3 then the v3 version is checked, otherwise the v2 version is used. func SkipIfVersionAtLeast(maxVersion string) { version := matchMajorAPIVersion(maxVersion) @@ -134,13 +134,13 @@ func ignoreAPIVersion() bool { type ccRoot struct { Links struct { - CloudContollerV2 struct { + CloudControllerV2 struct { Meta struct { Version string } } `json:"cloud_controller_v2"` - CloudContollerV3 struct { + CloudControllerV3 struct { Meta struct { Version string } @@ -172,7 +172,7 @@ func fetchAPIVersion() ccRoot { } func getAPIVersionV3() string { - return fetchAPIVersion().Links.CloudContollerV3.Meta.Version + return fetchAPIVersion().Links.CloudControllerV3.Meta.Version } // SkipIfNoRoutingAPI is used to skip tests if the routing API is not present diff --git a/integration/shared/isolated/internationalization_test.go b/integration/shared/isolated/internationalization_test.go index 260c061df23..ca55650c0d4 100644 --- a/integration/shared/isolated/internationalization_test.go +++ b/integration/shared/isolated/internationalization_test.go @@ -26,26 +26,26 @@ var _ = XDescribe("internationalization", func() { return helpers.CF("push", "--help") }), - Entry("when the the config and LANG environment variable is set, it uses config", func() *Session { + Entry("when the config and LANG environment variable is set, it uses config", func() *Session { session := helpers.CF("config", "--locale", "fr-FR") Eventually(session).Should(Exit(0)) return helpers.CFWithEnv(map[string]string{"LANG": "es-ES"}, "push", "--help") }), - Entry("when the the LANG environment variable is set", func() *Session { + Entry("when the LANG environment variable is set", func() *Session { return helpers.CFWithEnv(map[string]string{"LANG": "fr-FR"}, "push", "--help") }), - Entry("when the the LC_ALL environment variable is set", func() *Session { + Entry("when the LC_ALL environment variable is set", func() *Session { return helpers.CFWithEnv(map[string]string{"LC_ALL": "fr-FR"}, "push", "--help") }), - Entry("when the the LC_ALL and LANG environment variables are set, it uses LC_ALL", func() *Session { + Entry("when the LC_ALL and LANG environment variables are set, it uses LC_ALL", func() *Session { return helpers.CFWithEnv(map[string]string{"LC_ALL": "fr-FR", "LANG": "es-ES"}, "push", "--help") }), - Entry("when the the config, LC_ALL, and LANG is set, it uses config", func() *Session { + Entry("when the config, LC_ALL, and LANG is set, it uses config", func() *Session { session := helpers.CF("config", "--locale", "fr-FR") Eventually(session).Should(Exit(0)) @@ -60,15 +60,15 @@ var _ = XDescribe("internationalization", func() { Eventually(session).Should(Exit(0)) }, - Entry("when the the LANG and LC_ALL environment variable is not set", func() *Session { + Entry("when the LANG and LC_ALL environment variable is not set", func() *Session { return helpers.CF("push", "--help") }), - Entry("when the the LANG environment variable is set to a non-supported langauge", func() *Session { + Entry("when the LANG environment variable is set to a non-supported language", func() *Session { return helpers.CFWithEnv(map[string]string{"LANG": "jj-FF"}, "push", "--help") }), - Entry("when the the LC_ALL environment variable is set to a non-supported langauge", func() *Session { + Entry("when the LC_ALL environment variable is set to a non-supported language", func() *Session { return helpers.CFWithEnv(map[string]string{"LC_ALL": "jj-FF"}, "push", "--help") }), ) diff --git a/integration/v7/isolated/auth_command_test.go b/integration/v7/isolated/auth_command_test.go index e83633edb40..a8b675e627e 100644 --- a/integration/v7/isolated/auth_command_test.go +++ b/integration/v7/isolated/auth_command_test.go @@ -31,7 +31,7 @@ var _ = Describe("auth command", func() { Eventually(session).Should(Say("ENVIRONMENT VARIABLES:")) Eventually(session).Should(Say(`CF_USERNAME=user\s+Authenticating user. Overridden if USERNAME argument is provided.`)) - Eventually(session).Should(Say(`CF_PASSWORD=password\s+Password associated with user. Overriden if PASSWORD argument is provided.`)) + Eventually(session).Should(Say(`CF_PASSWORD=password\s+Password associated with user. Overridden if PASSWORD argument is provided.`)) Eventually(session).Should(Say("WARNING:")) Eventually(session).Should(Say("Providing your password as a command line option is highly discouraged")) @@ -276,7 +276,7 @@ var _ = Describe("auth command", func() { helpers.SkipIfUAAVersionAtLeast(uaaversion.MinUAAClientVersion) }) It("prints an error message", func() { - session := helpers.CF("auth", "some-username", "some-password", "--client-credentials", "--origin", "garbaje") + session := helpers.CF("auth", "some-username", "some-password", "--client-credentials", "--origin", "garbage") Eventually(session.Err).Should(Say("Option '--origin' requires UAA API version 4.19.0 or higher. Update your Cloud Foundry instance.")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) @@ -289,7 +289,7 @@ var _ = Describe("auth command", func() { }) When("--client-credentials is also set", func() { It("displays the appropriate error message", func() { - session := helpers.CF("auth", "some-username", "some-password", "--client-credentials", "--origin", "garbaje") + session := helpers.CF("auth", "some-username", "some-password", "--client-credentials", "--origin", "garbage") Eventually(session.Err).Should(Say("Incorrect Usage: The following arguments cannot be used together: --client-credentials, --origin")) Eventually(session).Should(Exit(1)) diff --git a/integration/v7/isolated/create_private_domain_test.go b/integration/v7/isolated/create_private_domain_test.go index 5e24d16b4d3..d67dc246aba 100644 --- a/integration/v7/isolated/create_private_domain_test.go +++ b/integration/v7/isolated/create_private_domain_test.go @@ -100,7 +100,7 @@ var _ = Describe("create-private-domain command", func() { }) }) - When("pre-existing domain", func() { + When("preexisting domain", func() { var privateDomain1 helpers.Domain BeforeEach(func() { privateDomain1 = helpers.NewDomain(orgName, helpers.NewDomainName()) diff --git a/integration/v7/isolated/create_service_command_test.go b/integration/v7/isolated/create_service_command_test.go index 986fafe22a7..8d2973c24cb 100644 --- a/integration/v7/isolated/create_service_command_test.go +++ b/integration/v7/isolated/create_service_command_test.go @@ -356,7 +356,7 @@ var _ = Describe("create-service command", func() { }) It("displays an informative message and exits 1", func() { - session := helpers.CF("create-service", "foo", "bar", serviceInstanceName, "-c", filepath.Join(emptyDir, "non-existent-file")) + session := helpers.CF("create-service", "foo", "bar", serviceInstanceName, "-c", filepath.Join(emptyDir, "nonexistent-file")) Eventually(session.Err).Should(Say("Invalid configuration provided for -c flag. Please provide a valid JSON object or path to a file containing a valid JSON object.")) Eventually(session).Should(Exit(1)) }) diff --git a/integration/v7/isolated/create_user_provided_service_command_test.go b/integration/v7/isolated/create_user_provided_service_command_test.go index 50253055cba..4a86b1f6174 100644 --- a/integration/v7/isolated/create_user_provided_service_command_test.go +++ b/integration/v7/isolated/create_user_provided_service_command_test.go @@ -67,7 +67,7 @@ var _ = Describe("create-user-provided-service command", func() { }) }) - When("an superflous argument is provided", func() { + When("an superfluous argument is provided", func() { It("fails and displays command usage", func() { session := helpers.CF("create-user-provided-service", "name", "extraparam") Eventually(session).Should(Exit(1)) @@ -94,7 +94,7 @@ var _ = Describe("create-user-provided-service command", func() { }) }) - When("targetting a space", func() { + When("targeting a space", func() { var ( userName string orgName string diff --git a/integration/v7/isolated/delete_command_test.go b/integration/v7/isolated/delete_command_test.go index 896ae58fb35..9d9e942dba1 100644 --- a/integration/v7/isolated/delete_command_test.go +++ b/integration/v7/isolated/delete_command_test.go @@ -94,7 +94,7 @@ var _ = Describe("delete command", func() { Expect(err).ToNot(HaveOccurred()) }) - It("shows more information when confiriming", func() { + It("shows more information when confirming", func() { username, _ := helpers.GetCredentials() session := helpers.CFWithStdin(buffer, "delete", "-r", appName) Eventually(session).Should(Say( diff --git a/integration/v7/isolated/delete_orphaned_routes_command_test.go b/integration/v7/isolated/delete_orphaned_routes_command_test.go index 6079739c767..e09b5c2911f 100644 --- a/integration/v7/isolated/delete_orphaned_routes_command_test.go +++ b/integration/v7/isolated/delete_orphaned_routes_command_test.go @@ -114,7 +114,7 @@ var _ = Describe("delete-orphaned-routes command", func() { }) }) - When("the user's inpiut is invalud", func() { + When("the user's input is invalid", func() { BeforeEach(func() { _, err := buffer.Write([]byte("abc\n")) Expect(err).ToNot(HaveOccurred()) diff --git a/integration/v7/isolated/delete_private_domain_command_test.go b/integration/v7/isolated/delete_private_domain_command_test.go index 36aa68854c6..1f17c1979f9 100644 --- a/integration/v7/isolated/delete_private_domain_command_test.go +++ b/integration/v7/isolated/delete_private_domain_command_test.go @@ -124,8 +124,8 @@ var _ = Describe("delete-private-domain command", func() { When("the domain doesn't exist", func() { It("displays OK and returns successfully", func() { - session := helpers.CFWithStdin(buffer, "delete-private-domain", "non-existent.com", "-f") - Eventually(session.Err).Should(Say(`Domain 'non-existent\.com' does not exist\.`)) + session := helpers.CFWithStdin(buffer, "delete-private-domain", "nonexistent.com", "-f") + Eventually(session.Err).Should(Say(`Domain 'nonexistent\.com' does not exist\.`)) Eventually(session).Should(Say("OK")) Eventually(session).Should(Exit(0)) }) diff --git a/integration/v7/isolated/delete_space_command_test.go b/integration/v7/isolated/delete_space_command_test.go index ef7a33caa86..9c6201426c1 100644 --- a/integration/v7/isolated/delete_space_command_test.go +++ b/integration/v7/isolated/delete_space_command_test.go @@ -194,7 +194,7 @@ var _ = Describe("delete-space command", func() { }) }) - When("the -o organzation does not exist", func() { + When("the -o organization does not exist", func() { BeforeEach(func() { helpers.LoginCF() }) diff --git a/integration/v7/isolated/delete_user_command_test.go b/integration/v7/isolated/delete_user_command_test.go index 6e4912632c9..d1632ad3544 100644 --- a/integration/v7/isolated/delete_user_command_test.go +++ b/integration/v7/isolated/delete_user_command_test.go @@ -106,8 +106,8 @@ var _ = Describe("delete-user command", func() { When("the user does not exist", func() { It("does not error but prints a message letting the user know it never existed", func() { - session := helpers.CF("delete-user", "non-existent-user", "-f") - Eventually(session).Should(Say("User 'non-existent-user' does not exist.")) + session := helpers.CF("delete-user", "nonexistent-user", "-f") + Eventually(session).Should(Say("User 'nonexistent-user' does not exist.")) Eventually(session).Should(Say("OK")) Eventually(session).Should(Exit(0)) }) diff --git a/integration/v7/isolated/events_command_test.go b/integration/v7/isolated/events_command_test.go index d8f7aafe8f6..7442cefaac5 100644 --- a/integration/v7/isolated/events_command_test.go +++ b/integration/v7/isolated/events_command_test.go @@ -75,7 +75,7 @@ var _ = Describe("events command", func() { It("displays events in the list", func() { - // Order of output is hard to assert here so we will just asseert we output only the events we expect and then rely on the unit + // Order of output is hard to assert here so we will just assert we output only the events we expect and then rely on the unit // tests to validate we are passing the `order_by=-created_at` query param to CAPI. The actual ordering is CAPIs concern. session := helpers.CF("events", appName) diff --git a/integration/v7/isolated/labels_command_test.go b/integration/v7/isolated/labels_command_test.go index 03d10e16c64..f2d45d7a2c0 100644 --- a/integration/v7/isolated/labels_command_test.go +++ b/integration/v7/isolated/labels_command_test.go @@ -111,9 +111,9 @@ var _ = Describe("labels command", func() { When("the app does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "app", "non-existent-app") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for app non-existent-app in org %s / space %s as %s...\n"), orgName, spaceName, username)) - Eventually(session.Err).Should(Say("App 'non-existent-app' not found")) + session := helpers.CF("labels", "app", "nonexistent-app") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for app nonexistent-app in org %s / space %s as %s...\n"), orgName, spaceName, username)) + Eventually(session.Err).Should(Say("App 'nonexistent-app' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -135,7 +135,7 @@ var _ = Describe("labels command", func() { Eventually(session).Should(Exit(0)) }) - It("fails if a non-existent stack is specified", func() { + It("fails if a nonexistent stack is specified", func() { session := helpers.CF("labels", "buildpack", buildpackName, "-s", "bogus-stack") Eventually(session.Err).Should(Say("Buildpack '%s' with stack 'bogus-stack' not found", buildpackName)) Eventually(session).Should(Say("FAILED")) @@ -211,7 +211,7 @@ var _ = Describe("labels command", func() { Eventually(session).Should(Exit(0)) }) - It("fails if a non-existent stack is specified", func() { + It("fails if a nonexistent stack is specified", func() { session := helpers.CF("labels", "buildpack", buildpackName, "-s", "bogus-stack") Eventually(session.Err).Should(Say("Buildpack '%s' with stack 'bogus-stack' not found", buildpackName)) Eventually(session).Should(Say("FAILED")) @@ -259,7 +259,7 @@ var _ = Describe("labels command", func() { Eventually(session).Should(Exit(1)) }) - It("fails when a non-existent stack is given", func() { + It("fails when a nonexistent stack is given", func() { session := helpers.CF("labels", "buildpack", buildpackName, "-s", "bogus-stack") Eventually(session.Err).Should(Say("Buildpack '%s' with stack 'bogus-stack' not found", buildpackName)) Eventually(session).Should(Say("FAILED")) @@ -306,7 +306,7 @@ var _ = Describe("labels command", func() { Eventually(session).Should(Exit(0)) }) - It("fails if a non-existent stack is specified", func() { + It("fails if a nonexistent stack is specified", func() { session := helpers.CF("labels", "buildpack", buildpackName, "-s", "bogus-stack") Eventually(session.Err).Should(Say("Buildpack '%s' with stack 'bogus-stack' not found", buildpackName)) Eventually(session).Should(Say("FAILED")) @@ -363,10 +363,10 @@ var _ = Describe("labels command", func() { When("the domain does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "domain", "non-existent-domain") + session := helpers.CF("labels", "domain", "nonexistent-domain") Eventually(session).Should(Exit(1)) - Expect(session).To(Say(regexp.QuoteMeta("Getting labels for domain non-existent-domain as %s...\n\n"), username)) - Expect(session.Err).To(Say("Domain 'non-existent-domain' not found")) + Expect(session).To(Say(regexp.QuoteMeta("Getting labels for domain nonexistent-domain as %s...\n\n"), username)) + Expect(session.Err).To(Say("Domain 'nonexistent-domain' not found")) Expect(session).To(Say("FAILED")) }) }) @@ -407,9 +407,9 @@ var _ = Describe("labels command", func() { When("the org does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "org", "non-existent-org") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for org %s as %s...\n\n"), "non-existent-org", username)) - Eventually(session.Err).Should(Say("Organization 'non-existent-org' not found")) + session := helpers.CF("labels", "org", "nonexistent-org") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for org %s as %s...\n\n"), "nonexistent-org", username)) + Eventually(session.Err).Should(Say("Organization 'nonexistent-org' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -469,9 +469,9 @@ var _ = Describe("labels command", func() { When("the route does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "route", "non-existent-route.example.com") + session := helpers.CF("labels", "route", "nonexistent-route.example.com") Eventually(session).Should(Exit(1)) - Expect(session).Should(Say(regexp.QuoteMeta("Getting labels for route non-existent-route.example.com in org %s / space %s as %s...\n"), orgName, spaceName, username)) + Expect(session).Should(Say(regexp.QuoteMeta("Getting labels for route nonexistent-route.example.com in org %s / space %s as %s...\n"), orgName, spaceName, username)) Expect(session.Err).To(Say("Domain 'example.com' not found")) Expect(session).To(Say("FAILED")) }) @@ -522,9 +522,9 @@ var _ = Describe("labels command", func() { When("the service broker does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "service-broker", "non-existent-broker") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-broker %s as %s...\n\n"), "non-existent-broker", username)) - Eventually(session.Err).Should(Say("Service broker 'non-existent-broker' not found")) + session := helpers.CF("labels", "service-broker", "nonexistent-broker") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-broker %s as %s...\n\n"), "nonexistent-broker", username)) + Eventually(session.Err).Should(Say("Service broker 'nonexistent-broker' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -574,9 +574,9 @@ var _ = Describe("labels command", func() { When("does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "service-instance", "non-existent-app") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-instance non-existent-app in org %s / space %s as %s...\n"), orgName, spaceName, username)) - Eventually(session.Err).Should(Say("Service instance 'non-existent-app' not found")) + session := helpers.CF("labels", "service-instance", "nonexistent-app") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-instance nonexistent-app in org %s / space %s as %s...\n"), orgName, spaceName, username)) + Eventually(session.Err).Should(Say("Service instance 'nonexistent-app' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -641,9 +641,9 @@ var _ = Describe("labels command", func() { When("the service offering does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "service-offering", "non-existent-offering") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-offering non-existent-offering as %s...\n"), username)) - Eventually(session.Err).Should(Say("Service offering 'non-existent-offering' not found")) + session := helpers.CF("labels", "service-offering", "nonexistent-offering") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-offering nonexistent-offering as %s...\n"), username)) + Eventually(session.Err).Should(Say("Service offering 'nonexistent-offering' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -709,9 +709,9 @@ var _ = Describe("labels command", func() { When("the service plan does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "service-plan", "non-existent-plan") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-plan non-existent-plan as %s...\n"), username)) - Eventually(session.Err).Should(Say("Service plan 'non-existent-plan' not found")) + session := helpers.CF("labels", "service-plan", "nonexistent-plan") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for service-plan nonexistent-plan as %s...\n"), username)) + Eventually(session.Err).Should(Say("Service plan 'nonexistent-plan' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -758,9 +758,9 @@ var _ = Describe("labels command", func() { When("the stack does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "stack", "non-existent-stack") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for stack %s as %s...\n\n"), "non-existent-stack", username)) - Eventually(session.Err).Should(Say("Stack 'non-existent-stack' not found")) + session := helpers.CF("labels", "stack", "nonexistent-stack") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for stack %s as %s...\n\n"), "nonexistent-stack", username)) + Eventually(session.Err).Should(Say("Stack 'nonexistent-stack' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -803,9 +803,9 @@ var _ = Describe("labels command", func() { When("the space does not exist", func() { It("displays an error", func() { - session := helpers.CF("labels", "space", "non-existent-space") - Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for space %s in org %s as %s...\n"), "non-existent-space", orgName, username)) - Eventually(session.Err).Should(Say("Space 'non-existent-space' not found")) + session := helpers.CF("labels", "space", "nonexistent-space") + Eventually(session).Should(Say(regexp.QuoteMeta("Getting labels for space %s in org %s as %s...\n"), "nonexistent-space", orgName, username)) + Eventually(session.Err).Should(Say("Space 'nonexistent-space' not found")) Eventually(session).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) diff --git a/integration/v7/isolated/login_command_test.go b/integration/v7/isolated/login_command_test.go index 70004c90b57..a4c5d0227e4 100644 --- a/integration/v7/isolated/login_command_test.go +++ b/integration/v7/isolated/login_command_test.go @@ -223,7 +223,7 @@ var _ = Describe("login command", func() { }) When("the API endpoint's scheme is https", func() { - // This test is somewhat redundant because the integration test setup will have alreayd logged in successfully with certificates at this point + // This test is somewhat redundant because the integration test setup will have already logged in successfully with certificates at this point // In the interest of test coverage however, we have decided to keep it in. When("the OS provides a valid SSL Certificate (Unix: SSL_CERT_FILE or SSL_CERT_DIR Environment variables) (Windows: Import-Certificate call)", func() { BeforeEach(func() { @@ -753,7 +753,7 @@ var _ = Describe("login command", func() { }) When("user does not select an organization", func() { - It("succesfully logs in but does not target any org", func() { + It("successfully logs in but does not target any org", func() { input := NewBuffer() _, err := input.Write([]byte("\n")) Expect(err).ToNot(HaveOccurred()) diff --git a/integration/v7/isolated/network_policies_command_test.go b/integration/v7/isolated/network_policies_command_test.go index 22fdba973dc..2181839bdba 100644 --- a/integration/v7/isolated/network_policies_command_test.go +++ b/integration/v7/isolated/network_policies_command_test.go @@ -175,7 +175,7 @@ var _ = Describe("network-policies command", func() { }) }) - When("policies are filtered by a non-existent source app", func() { + When("policies are filtered by a nonexistent source app", func() { It("returns an error", func() { session := helpers.CF("network-policies", "--source", "pineapple") diff --git a/integration/v7/isolated/oauth_token_command_test.go b/integration/v7/isolated/oauth_token_command_test.go index 38490c5322e..148e5f5df38 100644 --- a/integration/v7/isolated/oauth_token_command_test.go +++ b/integration/v7/isolated/oauth_token_command_test.go @@ -93,7 +93,7 @@ var _ = Describe("oauth-token command", func() { When("the oauth client ID and secret combination is invalid", func() { BeforeEach(func() { helpers.SetConfig(func(conf *configv3.Config) { - conf.ConfigFile.UAAOAuthClient = "non-existent-client" + conf.ConfigFile.UAAOAuthClient = "nonexistent-client" conf.ConfigFile.UAAOAuthClientSecret = "some-secret" }) }) @@ -151,7 +151,7 @@ var _ = Describe("oauth-token command", func() { When("the oauth client ID and secret combination is invalid", func() { BeforeEach(func() { helpers.SetConfig(func(conf *configv3.Config) { - conf.ConfigFile.UAAOAuthClient = "non-existent-client" + conf.ConfigFile.UAAOAuthClient = "nonexistent-client" conf.ConfigFile.UAAOAuthClientSecret = "some-secret" }) }) diff --git a/integration/v7/isolated/restage_command_test.go b/integration/v7/isolated/restage_command_test.go index eee7355cb49..1306df7d2bd 100644 --- a/integration/v7/isolated/restage_command_test.go +++ b/integration/v7/isolated/restage_command_test.go @@ -116,7 +116,7 @@ var _ = Describe("restage command", func() { session := helpers.CF("restage", appName) Eventually(session).Should(Say(`Restaging app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName)) - // The staticfile_buildback does compile an index.html file. However, it requires a "Staticfile" during buildpack detection. + // The staticfile_buildpack does compile an index.html file. However, it requires a "Staticfile" during buildpack detection. Eventually(session.Err).Should(Say("Error staging application: NoAppDetectedError - An app was not successfully detected by any available buildpack")) Eventually(session.Err).Should(Say(`TIP: Use 'cf buildpacks' to see a list of supported buildpacks.`)) Eventually(session).Should(Exit(1)) diff --git a/integration/v7/isolated/revisions_command_test.go b/integration/v7/isolated/revisions_command_test.go index ee014778750..8795bdb7479 100644 --- a/integration/v7/isolated/revisions_command_test.go +++ b/integration/v7/isolated/revisions_command_test.go @@ -49,7 +49,7 @@ var _ = Describe("revisions command", func() { }) }) - When("targetting and org and space", func() { + When("targeting and org and space", func() { BeforeEach(func() { helpers.SetupCF(orgName, spaceName) }) diff --git a/integration/v7/isolated/service_access_command_test.go b/integration/v7/isolated/service_access_command_test.go index 43384585d35..b69614a9b05 100644 --- a/integration/v7/isolated/service_access_command_test.go +++ b/integration/v7/isolated/service_access_command_test.go @@ -49,28 +49,28 @@ var _ = Describe("service-access command", func() { When("-b is provided with a broker name that does not exist", func() { It("shows an error message", func() { - session := helpers.CF("service-access", "-b", "non-existent-broker") + session := helpers.CF("service-access", "-b", "nonexistent-broker") Eventually(session).Should(Exit(1)) - Expect(session).To(Say(`Getting service access for broker non-existent-broker as %s\.\.\.`, userName)) - Expect(session.Err).To(Say(`(Service broker 'non-existent-broker' not found|No service offerings found for service broker 'non-existent-broker')\.`)) + Expect(session).To(Say(`Getting service access for broker nonexistent-broker as %s\.\.\.`, userName)) + Expect(session.Err).To(Say(`(Service broker 'nonexistent-broker' not found|No service offerings found for service broker 'nonexistent-broker')\.`)) }) }) When("-e is provided with a service name that does not exist", func() { It("shows an error message", func() { - session := helpers.CF("service-access", "-e", "non-existent-service") + session := helpers.CF("service-access", "-e", "nonexistent-service") Eventually(session).Should(Exit(1)) - Expect(session).To(Say(`Getting service access for service offering non-existent-service as %s\.\.\.`, userName)) - Expect(session.Err).To(Say(`Service offering 'non-existent-service' not found\.`)) + Expect(session).To(Say(`Getting service access for service offering nonexistent-service as %s\.\.\.`, userName)) + Expect(session.Err).To(Say(`Service offering 'nonexistent-service' not found\.`)) }) }) When("-o is provided with a org name that does not exist", func() { It("shows an error message", func() { - session := helpers.CF("service-access", "-o", "non-existent-org") + session := helpers.CF("service-access", "-o", "nonexistent-org") Eventually(session).Should(Exit(1)) - Expect(session).To(Say(`Getting service access for organization non-existent-org as %s\.\.\.`, userName)) - Expect(session.Err).To(Say(`Organization 'non-existent-org' not found`)) + Expect(session).To(Say(`Getting service access for organization nonexistent-org as %s\.\.\.`, userName)) + Expect(session.Err).To(Say(`Organization 'nonexistent-org' not found`)) }) }) diff --git a/integration/v7/isolated/set_label_command_test.go b/integration/v7/isolated/set_label_command_test.go index 40091753876..fcd7a321cd0 100644 --- a/integration/v7/isolated/set_label_command_test.go +++ b/integration/v7/isolated/set_label_command_test.go @@ -68,7 +68,7 @@ var _ = Describe("set-label command", func() { testExpectedBehaviors := func(resourceType, resourceTypeFormatted, resourceName string) { By("checking the behavior when the resource does not exist", func() { - unknownResourceName := "non-existent-" + resourceType + unknownResourceName := "nonexistent-" + resourceType session := helpers.CF("set-label", resourceType, unknownResourceName, "some-key=some-value") Eventually(session.Err).Should(Say("%s '%s' not found", resourceTypeFormatted, unknownResourceName)) Eventually(session).Should(Say("FAILED")) @@ -331,9 +331,9 @@ var _ = Describe("set-label command", func() { When("the route is unknown", func() { It("displays an error", func() { - invalidRoute := "non-existent-host." + domainName + invalidRoute := "nonexistent-host." + domainName session := helpers.CF("set-label", "route", invalidRoute, "some-key=some-value") - Eventually(session.Err).Should(Say(`Route with host 'non-existent-host', domain '%s', and path '/' not found\.`, domainName)) + Eventually(session.Err).Should(Say(`Route with host 'nonexistent-host', domain '%s', and path '/' not found\.`, domainName)) Eventually(session.Out).Should(Say("FAILED")) Eventually(session).Should(Exit(1)) }) @@ -510,7 +510,7 @@ var _ = Describe("set-label command", func() { }) }) - When("a non-existent stack is specified", func() { + When("a nonexistent stack is specified", func() { It("displays an error", func() { bogusStackName := stacks[0] + "-bogus-" + stacks[1] session := helpers.CF("set-label", "buildpack", buildpackName, "olive=3", "mangosteen=4", "--stack", bogusStackName) @@ -575,7 +575,7 @@ var _ = Describe("set-label command", func() { }) }) - When("a non-existent stack is specified", func() { + When("a nonexistent stack is specified", func() { It("displays an error", func() { bogusStackName := stacks[0] + "-bogus-" + stacks[1] session := helpers.CF("set-label", "buildpack", buildpackName, "olive=3", "mangosteen=4", "--stack", bogusStackName) diff --git a/integration/v7/isolated/share_route_command_test.go b/integration/v7/isolated/share_route_command_test.go index 68cbc538bcb..c1a646982a2 100644 --- a/integration/v7/isolated/share_route_command_test.go +++ b/integration/v7/isolated/share_route_command_test.go @@ -56,7 +56,7 @@ var _ = Describe("share route command", func() { }) }) - When("the environment is set up conrrectly", func() { + When("the environment is set up correctly", func() { var ( userName string orgName string @@ -76,7 +76,7 @@ var _ = Describe("share route command", func() { helpers.QuickDeleteOrg(orgName) }) - When("the domain extists", func() { + When("the domain exists", func() { var ( domainName string targetSpaceName string diff --git a/integration/v7/isolated/space_ssh_allowed_command_test.go b/integration/v7/isolated/space_ssh_allowed_command_test.go index a115fa64834..b6203ef9610 100644 --- a/integration/v7/isolated/space_ssh_allowed_command_test.go +++ b/integration/v7/isolated/space_ssh_allowed_command_test.go @@ -65,7 +65,7 @@ var _ = Describe("space-ssh-allowed command", func() { When("the space does not exist", func() { BeforeEach(func() { - spaceName = "non-existent-space" + spaceName = "nonexistent-space" }) It("displays a missing space error message and fails", func() { diff --git a/integration/v7/isolated/unset_label_command_test.go b/integration/v7/isolated/unset_label_command_test.go index c63edc1a945..813312b170a 100644 --- a/integration/v7/isolated/unset_label_command_test.go +++ b/integration/v7/isolated/unset_label_command_test.go @@ -139,9 +139,9 @@ var _ = Describe("unset-label command", func() { When("the buildpack is unknown", func() { It("displays an error", func() { - session := helpers.CF("unset-label", "buildpack", "non-existent-buildpack", "some-key=some-value") + session := helpers.CF("unset-label", "buildpack", "nonexistent-buildpack", "some-key=some-value") Eventually(session).Should(Exit(1)) - Expect(session.Err).Should(Say("Buildpack 'non-existent-buildpack' not found")) + Expect(session.Err).Should(Say("Buildpack 'nonexistent-buildpack' not found")) Expect(session).Should(Say("FAILED")) }) }) diff --git a/integration/v7/isolated/update_user_provided_service_command_test.go b/integration/v7/isolated/update_user_provided_service_command_test.go index 2556ceb3895..833ed47bffc 100644 --- a/integration/v7/isolated/update_user_provided_service_command_test.go +++ b/integration/v7/isolated/update_user_provided_service_command_test.go @@ -108,10 +108,10 @@ var _ = Describe("update-user-provided-service command", func() { When("the user-provided service instance does not exist", func() { It("displays an informative error and exits 1", func() { - session := helpers.CF("update-user-provided-service", "non-existent-service", "-l", "syslog://example.com") + session := helpers.CF("update-user-provided-service", "nonexistent-service", "-l", "syslog://example.com") Eventually(session).Should(Exit(1)) - Expect(session.Err).To(Say("Service instance 'non-existent-service' not found")) + Expect(session.Err).To(Say("Service instance 'nonexistent-service' not found")) Expect(session.Out).To(Say("FAILED")) }) }) diff --git a/plugin/plugin_examples/basic_plugin.go b/plugin/plugin_examples/basic_plugin.go index f3d7f90d76a..b3662d08644 100644 --- a/plugin/plugin_examples/basic_plugin.go +++ b/plugin/plugin_examples/basic_plugin.go @@ -15,7 +15,7 @@ type BasicPlugin struct{} // // Run(....) is the entry point when the core CLI is invoking a command defined // by a plugin. The first parameter, plugin.CliConnection, is a struct that can -// be used to invoke cli commands. The second paramter, args, is a slice of +// be used to invoke cli commands. The second parameter, args, is a slice of // strings. args[0] will be the name of the command, and will be followed by // any additional arguments a cli user typed in. // @@ -35,7 +35,7 @@ func (c *BasicPlugin) Run(cliConnection plugin.CliConnection, args []string) { // GetMetadata() returns a PluginMetadata struct. The first field, Name, // determines the name of the plugin which should generally be without spaces. // If there are spaces in the name a user will need to properly quote the name -// during uninstall otherwise the name will be treated as seperate arguments. +// during uninstall otherwise the name will be treated as separate arguments. // The second value is a slice of Command structs. Our slice only contains one // Command Struct, but could contain any number of them. The first field Name // defines the command `cf basic-plugin-command` once installed into the CLI. The diff --git a/plugin/plugin_examples/echo.go b/plugin/plugin_examples/echo.go index 770cc8db807..3a0e8f46215 100644 --- a/plugin/plugin_examples/echo.go +++ b/plugin/plugin_examples/echo.go @@ -25,7 +25,7 @@ func main() { func (pluginDemo *PluginDemonstratingParams) Run(cliConnection plugin.CliConnection, args []string) { // Initialize flags echoFlagSet := flag.NewFlagSet("echo", flag.ExitOnError) - uppercase := echoFlagSet.Bool("uppercase", false, "displayes all provided text in uppercase") + uppercase := echoFlagSet.Bool("uppercase", false, "displayed all provided text in uppercase") // Parse starting from [1] because the [0]th element is the // name of the command diff --git a/resources/droplet_resource.go b/resources/droplet_resource.go index 515bfc61133..27dc2574f3f 100644 --- a/resources/droplet_resource.go +++ b/resources/droplet_resource.go @@ -28,7 +28,7 @@ type Droplet struct { type DropletBuildpack struct { // Name is the buildpack name. Name string `json:"name"` - // BuildpackName is the the name reported by the buildpack. + // BuildpackName is the name reported by the buildpack. BuildpackName string `json:"buildpack_name"` // DetectOutput is the output of the buildpack detect script. DetectOutput string `json:"detect_output"` diff --git a/util/clissh/ssh_test.go b/util/clissh/ssh_test.go index 05cf6da870a..c232f1c3661 100644 --- a/util/clissh/ssh_test.go +++ b/util/clissh/ssh_test.go @@ -478,7 +478,7 @@ var _ = Describe("CLI SSH", func() { Expect(fakeSecureSession.ShellCallCount()).To(Equal(1)) }) - It("does not not restore the terminal", func() { + It("does not restore the terminal", func() { Expect(fakeSecureSession.ShellCallCount()).To(Equal(1)) Expect(fakeTerminalHelper.SetRawTerminalCallCount()).To(Equal(1)) Expect(fakeTerminalHelper.RestoreTerminalCallCount()).To(Equal(0)) @@ -578,7 +578,7 @@ var _ = Describe("CLI SSH", func() { }) }) - When("a command is specifed", func() { + When("a command is specified", func() { BeforeEach(func() { commands = []string{"echo", "-n", "hello"} }) diff --git a/util/configv3/plugins_config_test.go b/util/configv3/plugins_config_test.go index 58e92819a29..d537db68fbf 100644 --- a/util/configv3/plugins_config_test.go +++ b/util/configv3/plugins_config_test.go @@ -308,7 +308,7 @@ var _ = Describe("PluginsConfig", func() { setPluginConfig(pluginsPath, rawConfig) }) - It("returns the pluging sorted by name", func() { + It("returns the plugins sorted by name", func() { config, err := LoadConfig() Expect(err).ToNot(HaveOccurred()) Expect(config.Plugins()).To(Equal([]Plugin{ diff --git a/util/manifest/application.go b/util/manifest/application.go index e539a719028..b748c1753f0 100644 --- a/util/manifest/application.go +++ b/util/manifest/application.go @@ -150,7 +150,7 @@ func (app *Application) UnmarshalYAML(unmarshaller func(interface{}) error) erro } // "null" values are identical to non-existant values in YAML. In order to - // detect if an explicit null is given, a manual existance check is required. + // detect if an explicit null is given, a manual existence check is required. exists := map[string]interface{}{} err = unmarshaller(&exists) if err != nil { diff --git a/util/manifestparser/application.go b/util/manifestparser/application.go index 4eae68b1ecc..3b12942dfcc 100644 --- a/util/manifestparser/application.go +++ b/util/manifestparser/application.go @@ -61,7 +61,7 @@ func (application *Application) SetStartCommand(command string) { func (application *Application) UnmarshalYAML(unmarshal func(v interface{}) error) error { // This prevents infinite recursion. The alias type does not implement the unmarshaller interface - // so by casting application to a alias pointer, it will unmarshal in to the same memory without calling + // so by casting application to a alias pointer, it will unmarshal into the same memory without calling // UnmarshalYAML on itself infinite times type Alias Application aliasPntr := (*Alias)(application) diff --git a/util/manifestparser/application_test.go b/util/manifestparser/application_test.go index d39eb28db8a..fb8b8ce9ac7 100644 --- a/util/manifestparser/application_test.go +++ b/util/manifestparser/application_test.go @@ -485,7 +485,7 @@ log-rate-limit-per-second: 5K app.RemainingManifestFields = map[string]interface{}{} }) - It("sets the buildpackks in the map", func() { + It("sets the buildpacks in the map", func() { Expect(app.RemainingManifestFields["buildpacks"]).To(ConsistOf("bp1", "bp2")) }) diff --git a/util/manifestparser/process.go b/util/manifestparser/process.go index 6481225a6af..07d2c35bc54 100644 --- a/util/manifestparser/process.go +++ b/util/manifestparser/process.go @@ -34,7 +34,7 @@ func (process *Process) SetStartCommand(command string) { func (process *Process) UnmarshalYAML(unmarshal func(v interface{}) error) error { // This prevents infinite recursion. The Alias type does not implement the unmarshaller interface - // so by casting application to a alias pointer, it will unmarshal in to the same memory without calling + // so by casting application to a alias pointer, it will unmarshal into the same memory without calling // UnmarshalYAML on itself infinite times type Alias Process aliasPntr := (*Alias)(process) diff --git a/util/randomword/generator.go b/util/randomword/generator.go index 06eba5f22ab..d7c97a5ba6c 100644 --- a/util/randomword/generator.go +++ b/util/randomword/generator.go @@ -83,7 +83,7 @@ wacky wise zany` -const nouns = `ardvark +const nouns = `aardvark alligator antelope baboon @@ -170,7 +170,7 @@ rabbit ratel raven reedbuck -rhinocerous +rhinoceros roan sable serval diff --git a/util/ui/i18n.go b/util/ui/i18n.go index 38b5c101f37..559a0e6db22 100644 --- a/util/ui/i18n.go +++ b/util/ui/i18n.go @@ -63,7 +63,7 @@ func GetTranslationFunc(reader LocaleReader) (TranslateFunc, error) { } // ParseLocale will return a locale formatted as "-" for all non-Chinese lanagues. For Chinese, it will return +// code>" for all non-Chinese languages. For Chinese, it will return // "zh-