Skip to content

Commit

Permalink
Cloud CLI Kill Switch and LD Flag Caching (#1274)
Browse files Browse the repository at this point in the history
* kill switch and flag cache

* set flag cache expiration to 1 hr

* passing tests

* fmt

* remove print

* allow mds login

* fix import cycle

* cleanup test file

* validate cached flags are for correct ld user

* pr comments

* fix failed test

* update comment

* test and refactor

* trigger ci

* check for both test urls

* extract timeout into var

Co-authored-by: Brian Strauch <[email protected]>
  • Loading branch information
mtodzo and brianstrauch authored May 19, 2022
1 parent 267b08c commit 8eeef36
Show file tree
Hide file tree
Showing 27 changed files with 376 additions and 275 deletions.
3 changes: 2 additions & 1 deletion cmd/docs/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"github.com/confluentinc/cli/internal/pkg/ccloudv2"
"os"

"github.com/confluentinc/cli/internal/cmd"
Expand All @@ -24,7 +25,7 @@ func main() {
// Auto-generate documentation for cloud and on-prem commands.
configs := []*v1.Config{
{
Contexts: map[string]*v1.Context{"Cloud": {PlatformName: v1.CCloudHostnames[0]}},
Contexts: map[string]*v1.Context{"Cloud": {PlatformName: ccloudv2.Hostnames[0]}},
CurrentContext: "Cloud",
},
{
Expand Down
3 changes: 2 additions & 1 deletion cmd/lint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"flag"
"fmt"
"github.com/confluentinc/cli/internal/pkg/ccloudv2"
"os"
"strings"

Expand Down Expand Up @@ -289,7 +290,7 @@ func main() {
CurrentContext: "no context",
},
{
Contexts: map[string]*v1.Context{"cloud": {PlatformName: v1.CCloudHostnames[0]}},
Contexts: map[string]*v1.Context{"cloud": {PlatformName: ccloudv2.Hostnames[0]}},
CurrentContext: "cloud",
},
{
Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,6 @@ github.com/confluentinc/cc-utils/idgen v0.203.0/go.mod h1:vKsrR9u7tqRE/9MP1sQn0b
github.com/confluentinc/ccloud-sdk-go-v1 v0.0.105 h1:rJjoMIEEjChHHTAkp/bxxzFR2XnU4SCAz6bgRp3vqOw=
github.com/confluentinc/ccloud-sdk-go-v1 v0.0.105/go.mod h1:gFne0gmsbgH4GlpfmwadKrAMuSEvEB+mP4DfkEJ4Zi4=
github.com/confluentinc/ccloud-sdk-go-v2-internal/networking v0.0.5/go.mod h1:b0fKj4FlWseVcd8CsjXUdvvDtAGHu8nDUJjPykqWInQ=
github.com/confluentinc/ccloud-sdk-go-v2-internal/networking v0.0.5/go.mod h1:b0fKj4FlWseVcd8CsjXUdvvDtAGHu8nDUJjPykqWInQ=
github.com/confluentinc/ccloud-sdk-go-v2/cmk v0.6.0 h1:qOBunYd2bWo/IikZ60xMDcp/KfZ/ZahCOoeTjxXOoRU=
github.com/confluentinc/ccloud-sdk-go-v2/cmk v0.6.0/go.mod h1:357Zo3HvVAe5iQgUFxUbQPAKJasGm8vFMkOB+krVmR8=
github.com/confluentinc/ccloud-sdk-go-v2/iam v0.6.0 h1:NCEZ7nR76RThfu4oCBguMf+IHRdOr3KtAoYbzMJr18o=
Expand Down
5 changes: 2 additions & 3 deletions internal/cmd/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"fmt"
"os"

launchdarkly "github.com/confluentinc/cli/internal/pkg/launch-darkly"

shell "github.com/brianstrauch/cobra-shell"
"github.com/confluentinc/ccloud-sdk-go-v1"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -37,6 +35,7 @@ import (
"github.com/confluentinc/cli/internal/pkg/config/load"
v1 "github.com/confluentinc/cli/internal/pkg/config/v1"
"github.com/confluentinc/cli/internal/pkg/errors"
"github.com/confluentinc/cli/internal/pkg/featureflags"
"github.com/confluentinc/cli/internal/pkg/form"
"github.com/confluentinc/cli/internal/pkg/help"
"github.com/confluentinc/cli/internal/pkg/netrc"
Expand Down Expand Up @@ -76,7 +75,7 @@ func NewConfluentCommand(cfg *v1.Config, isTest bool, ver *pversion.Version) *co
loginCredentialsManager := pauth.NewLoginCredentialsManager(netrcHandler, form.NewPrompt(os.Stdin), getCloudClient(cfg, ccloudClientFactory))
loginOrganizationManager := pauth.NewLoginOrganizationManagerImpl()
mdsClientManager := &pauth.MDSClientManagerImpl{}
launchdarkly.InitManager(ver, isTest)
featureflags.Init(ver, isTest)

prerunner := &pcmd.PreRun{
AuthTokenHandler: authTokenHandler,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/kafka/command_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func (suite *KafkaClusterTestSuite) TestGetLkcForDescribe() {
cfg := v1.AuthenticatedCloudConfigMock()
prerunner := &pcmd.PreRun{Config: cfg}
c := &clusterCommand{pcmd.NewAuthenticatedStateFlagCommand(cmd, prerunner)}
c.Config = dynamicconfig.NewDynamicConfig(cfg, nil, nil)
c.Config = dynamicconfig.New(cfg, nil, nil)
lkc, err := c.getLkcForDescribe([]string{"lkc-123"})
req.Equal("lkc-123", lkc)
req.NoError(err)
Expand Down
19 changes: 4 additions & 15 deletions internal/cmd/login/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import (
"github.com/confluentinc/ccloud-sdk-go-v1"
"github.com/spf13/cobra"

"github.com/confluentinc/cli/internal/pkg/ccloudv2"

pauth "github.com/confluentinc/cli/internal/pkg/auth"
pcmd "github.com/confluentinc/cli/internal/pkg/cmd"
v1 "github.com/confluentinc/cli/internal/pkg/config/v1"
"github.com/confluentinc/cli/internal/pkg/errors"
"github.com/confluentinc/cli/internal/pkg/log"
"github.com/confluentinc/cli/internal/pkg/netrc"
"github.com/confluentinc/cli/internal/pkg/utils"
testserver "github.com/confluentinc/cli/test/test-server"
)

type command struct {
Expand Down Expand Up @@ -73,7 +74,7 @@ func (c *command) login(cmd *cobra.Command, _ []string) error {
return err
}

isCCloud := c.isCCloudURL(url)
isCCloud := ccloudv2.IsCCloudURL(url, c.isTest)

url, warningMsg, err := validateURL(url, isCCloud)
if err != nil {
Expand Down Expand Up @@ -293,7 +294,7 @@ func (c *command) saveLoginToNetrc(cmd *cobra.Command, isCloud bool, credentials

func validateURL(url string, isCCloud bool) (string, string, error) {
if isCCloud {
for _, hostname := range v1.CCloudHostnames {
for _, hostname := range ccloudv2.Hostnames {
if strings.Contains(url, hostname) {
if !strings.HasSuffix(strings.TrimSuffix(url, "/"), hostname) {
return url, "", errors.NewErrorWithSuggestions(errors.UnneccessaryUrlFlagForCloudLoginErrorMsg, errors.UnneccessaryUrlFlagForCloudLoginSuggestions)
Expand Down Expand Up @@ -338,18 +339,6 @@ func validateURL(url string, isCCloud bool) (string, string, error) {
return url, strings.Join(msg, " and "), nil
}

func (c *command) isCCloudURL(url string) bool {
for _, hostname := range v1.CCloudHostnames {
if strings.Contains(url, hostname) {
return true
}
}
if c.isTest {
return strings.Contains(url, testserver.TestCloudURL.Host)
}
return false
}

func (c *command) getOrgResourceId(cmd *cobra.Command) (string, error) {
return pauth.GetLoginOrganization(
c.loginOrganizationManager.GetLoginOrganizationFromArgs(cmd),
Expand Down
27 changes: 0 additions & 27 deletions internal/cmd/login/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -978,30 +978,3 @@ func verifyLoggedOutState(t *testing.T, cfg *v1.Config, loggedOutContext string)
req.Empty(state.AuthToken)
req.Empty(state.Auth)
}

func TestIsCCloudURL_True(t *testing.T) {
for _, url := range []string{
"confluent.cloud",
"https://confluent.cloud",
"https://devel.cpdev.cloud/",
"devel.cpdev.cloud",
"stag.cpdev.cloud",
"premium-oryx.gcp.priv.cpdev.cloud",
} {
c := new(command)
isCCloud := c.isCCloudURL(url)
require.True(t, isCCloud, url+" should return true")
}
}

func TestIsCCloudURL_False(t *testing.T) {
for _, url := range []string{
"example.com",
"example.com:8090",
"https://example.com",
} {
c := new(command)
isCCloud := c.isCCloudURL(url)
require.False(t, isCCloud, url+" should return false")
}
}
4 changes: 2 additions & 2 deletions internal/cmd/schema-registry/command_schema_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"strings"

"github.com/antihax/optional"
pcmd "github.com/confluentinc/cli/internal/pkg/cmd"
"github.com/confluentinc/cli/internal/pkg/output"
srsdk "github.com/confluentinc/schema-registry-sdk-go"
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/internal/pkg/cmd"
"github.com/confluentinc/cli/internal/pkg/errors"
"github.com/confluentinc/cli/internal/pkg/examples"
"github.com/confluentinc/cli/internal/pkg/output"
"github.com/confluentinc/cli/internal/pkg/utils"
pversion "github.com/confluentinc/cli/internal/pkg/version"
)
Expand Down
5 changes: 3 additions & 2 deletions internal/cmd/schema-registry/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import (
"context"
"os"

dynamicconfig "github.com/confluentinc/cli/internal/pkg/dynamic-config"
"github.com/confluentinc/cli/internal/pkg/log"
srsdk "github.com/confluentinc/schema-registry-sdk-go"
"github.com/spf13/cobra"

dynamicconfig "github.com/confluentinc/cli/internal/pkg/dynamic-config"
"github.com/confluentinc/cli/internal/pkg/log"

pauth "github.com/confluentinc/cli/internal/pkg/auth"
v1 "github.com/confluentinc/cli/internal/pkg/config/v1"
"github.com/confluentinc/cli/internal/pkg/errors"
Expand Down
17 changes: 16 additions & 1 deletion internal/pkg/ccloudv2/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,31 @@ import (
orgv2 "github.com/confluentinc/ccloud-sdk-go-v2/org/v2"

plog "github.com/confluentinc/cli/internal/pkg/log"
testserver "github.com/confluentinc/cli/test/test-server"
)

const (
pageTokenQueryParameter = "page_token"
ccloudV2ListPageSize = 100
)

var Hostnames = []string{"confluent.cloud", "cpdev.cloud"}

func IsCCloudURL(url string, isTest bool) bool {
for _, hostname := range Hostnames {
if strings.Contains(url, hostname) {
return true
}
}
if isTest {
return strings.Contains(url, testserver.TestCloudURL.Host) || strings.Contains(url, testserver.TestV2CloudURL.Host)
}
return false
}

func getServerUrl(baseURL string, isTest bool) string {
if isTest {
return "http://127.0.0.1:2048"
return testserver.TestV2CloudURL.String()
}
if strings.Contains(baseURL, "devel") {
return "https://api.devel.cpdev.cloud"
Expand Down
31 changes: 31 additions & 0 deletions internal/pkg/ccloudv2/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ccloudv2

import (
"github.com/stretchr/testify/require"
"testing"
)

func TestIsCCloudURL_True(t *testing.T) {
for _, url := range []string{
"confluent.cloud",
"https://confluent.cloud",
"https://devel.cpdev.cloud/",
"devel.cpdev.cloud",
"stag.cpdev.cloud",
"premium-oryx.gcp.priv.cpdev.cloud",
} {
isCCloud := IsCCloudURL(url, false)
require.True(t, isCCloud, url+" should return true")
}
}

func TestIsCCloudURL_False(t *testing.T) {
for _, url := range []string{
"example.com",
"example.com:8090",
"https://example.com",
} {
isCCloud := IsCCloudURL(url, false)
require.False(t, isCCloud, url+" should return false")
}
}
51 changes: 51 additions & 0 deletions internal/pkg/cmd/prerunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"os"
"strings"

launchdarkly "github.com/confluentinc/cli/internal/pkg/featureflags"

dynamicconfig "github.com/confluentinc/cli/internal/pkg/dynamic-config"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -188,6 +190,16 @@ func (r *PreRun) Anonymous(command *CLICommand, willAuthenticate bool) func(cmd
if err := command.Config.InitDynamicConfig(cmd, r.Config); err != nil {
return err
}

// check Feature Flag "cli.disable" for commands run from cloud context (except for on-prem login)
// check for commands that require cloud auth (since cloud context might not be active until auto-login)
// check for cloud login (since it is not executed from cloud context)
if (!isOnPremLoginCmd(command, r.IsTest) && r.Config.IsCloudLogin()) || CommandRequiresCloudAuth(command.Command, command.Config.Config) || isCloudLoginCmd(command, r.IsTest) {
if err := checkCliDisable(command, r.Config); err != nil {
return err
}
}

if err := log.SetLoggingVerbosity(cmd, log.CliLogger); err != nil {
return err
}
Expand All @@ -214,6 +226,45 @@ func (r *PreRun) Anonymous(command *CLICommand, willAuthenticate bool) func(cmd
}
}

func checkCliDisable(cmd *CLICommand, config *v1.Config) error {
ldDisableJson := launchdarkly.Manager.JsonVariation("cli.disable", cmd.Config.Context(), nil)
ldDisable, ok := ldDisableJson.(map[string]interface{})
if !ok {
return nil
}
errMsg, errMsgOk := ldDisable["error_msg"].(string)
if errMsgOk && errMsg != "" {
allowUpdate, allowUpdateOk := ldDisable["allow_update"].(bool)
if !(cmd.CommandPath() == "confluent update" && allowUpdateOk && allowUpdate) {
// in case a user is trying to run an on-prem command from a cloud context (should not see LD msg)
if err := ErrIfMissingRunRequirement(cmd.Command, config); err != nil && err == requireOnPremLoginErr {
return err
}
suggestionsMsg, _ := ldDisable["suggestions_msg"].(string)
return errors.NewErrorWithSuggestions(errMsg, suggestionsMsg)
}
}
return nil
}

func isOnPremLoginCmd(command *CLICommand, isTest bool) bool {
if command.CommandPath() != "confluent login" {
return false
}
mdsEnvUrl := pauth.GetEnvWithFallback(pauth.ConfluentPlatformMDSURL, pauth.DeprecatedConfluentPlatformMDSURL)
urlFlag, _ := command.Flags().GetString("url")
return (urlFlag == "" && mdsEnvUrl != "") || !ccloudv2.IsCCloudURL(urlFlag, isTest)
}

func isCloudLoginCmd(command *CLICommand, isTest bool) bool {
if command.CommandPath() != "confluent login" {
return false
}
mdsEnvUrl := pauth.GetEnvWithFallback(pauth.ConfluentPlatformMDSURL, pauth.DeprecatedConfluentPlatformMDSURL)
urlFlag, _ := command.Flags().GetString("url")
return (urlFlag == "" && mdsEnvUrl == "") || ccloudv2.IsCCloudURL(urlFlag, isTest)
}

func LabelRequiredFlags(cmd *cobra.Command) {
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if IsFlagRequired(flag) {
Expand Down
4 changes: 4 additions & 0 deletions internal/pkg/cmd/prerunner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strings"
"testing"

launchdarkly "github.com/confluentinc/cli/internal/pkg/featureflags"

flowv1 "github.com/confluentinc/cc-structs/kafka/flow/v1"
orgv1 "github.com/confluentinc/cc-structs/kafka/org/v1"
"github.com/confluentinc/ccloud-sdk-go-v1"
Expand Down Expand Up @@ -114,6 +116,8 @@ func getPreRunBase() *pcmd.PreRun {
}

func TestPreRun_Anonymous_SetLoggingLevel(t *testing.T) {
launchdarkly.Init(nil, true)

tests := map[string]log.Level{
"": log.ERROR,
"-v": log.WARN,
Expand Down
18 changes: 18 additions & 0 deletions internal/pkg/cmd/run_requirements.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,21 @@ func ErrIfMissingRunRequirement(cmd *cobra.Command, cfg *v1.Config) error {

return ErrIfMissingRunRequirement(cmd.Parent(), cfg)
}

func CommandRequiresCloudAuth(cmd *cobra.Command, cfg *v1.Config) bool {
if requirement, ok := cmd.Annotations[RunRequirement]; ok {
switch requirement {
case RequireCloudLogin:
return true
case RequireNonAPIKeyCloudLogin:
return true
case RequireCloudLoginOrOnPremLogin:
return cfg.IsCloudLogin()
case RequireNonAPIKeyCloudLoginOrOnPremLogin:
return cfg.IsCloudLogin()
case RequireOnPremLogin:
return false
}
}
return false
}
4 changes: 4 additions & 0 deletions internal/pkg/cmd/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"

"github.com/spf13/cobra"

launchdarkly "github.com/confluentinc/cli/internal/pkg/featureflags"
)

// ExecuteCommand runs the root command with the given args, and returns the output string or an error.
Expand All @@ -23,6 +25,8 @@ func ExecuteCommandC(root *cobra.Command, args ...string) (c *cobra.Command, out

c, err = root.ExecuteC()

launchdarkly.Init(nil, true)

return c, buf.String(), err
}

Expand Down
3 changes: 1 addition & 2 deletions internal/pkg/config/v1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ const (
)

var (
ver, _ = version.NewVersion("1.0.0")
CCloudHostnames = []string{"confluent.cloud", "cpdev.cloud"}
ver, _ = version.NewVersion("1.0.0")
)

// Config represents the CLI configuration.
Expand Down
Loading

0 comments on commit 8eeef36

Please sign in to comment.