[CLI] Adding the ability to create a default org for backends that support orgs (#8352)
This commit is contained in:
parent
0a38bc295c
commit
74ba28ad55
|
@ -1,5 +1,9 @@
|
|||
### Improvements
|
||||
|
||||
- [CLI] Adding the ability to use `pulumi org set [name]` to set a default org
|
||||
to use when creating a stacks in the Pulumi Service backend or Self -hosted Service
|
||||
[#8352](https://github.com/pulumi/pulumi/pull/8352)
|
||||
|
||||
- [schema] Add IsOverlay option to disable codegen for particular types
|
||||
[#8338](https://github.com/pulumi/pulumi/pull/8338)
|
||||
|
||||
|
|
|
@ -229,6 +229,10 @@ func loginWithBrowser(ctx context.Context, d diag.Sink, cloudURL string, opts di
|
|||
return New(d, cloudURL)
|
||||
}
|
||||
|
||||
func SetDefaultOrg(url string, orgName string) error {
|
||||
return workspace.SetBackendConfigDefaultOrg(url, orgName)
|
||||
}
|
||||
|
||||
// Login logs into the target cloud URL and returns the cloud backend for it.
|
||||
func Login(ctx context.Context, d diag.Sink, cloudURL string, opts display.Options) (Backend, error) {
|
||||
cloudURL = ValueOrDefaultURL(cloudURL)
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
|
||||
func newLoginCmd() *cobra.Command {
|
||||
var cloudURL string
|
||||
var defaultOrg string
|
||||
var localMode bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -55,6 +56,9 @@ func newLoginCmd() *cobra.Command {
|
|||
"to log in to a self-hosted Pulumi service running at the api.pulumi.acmecorp.com domain.\n" +
|
||||
"\n" +
|
||||
"For `https://` URLs, the CLI will speak REST to a service that manages state and concurrency control.\n" +
|
||||
"You can specify a default org to use when logging into the Pulumi service backend or a " +
|
||||
"self-hosted Pulumi service.\n" +
|
||||
"\n" +
|
||||
"[PREVIEW] If you prefer to operate Pulumi independently of a service, and entirely local to your computer,\n" +
|
||||
"pass `file://<path>`, where `<path>` will be where state checkpoints will be stored. For instance,\n" +
|
||||
"\n" +
|
||||
|
@ -127,8 +131,21 @@ func newLoginCmd() *cobra.Command {
|
|||
var err error
|
||||
if filestate.IsFileStateBackendURL(cloudURL) {
|
||||
be, err = filestate.Login(cmdutil.Diag(), cloudURL)
|
||||
if defaultOrg != "" {
|
||||
return fmt.Errorf("unable to set default org for this type of backend")
|
||||
}
|
||||
} else {
|
||||
be, err = httpstate.Login(commandContext(), cmdutil.Diag(), cloudURL, displayOptions)
|
||||
// if the user has specified a default org to associate with the backend
|
||||
if defaultOrg != "" {
|
||||
cloudURL, err := workspace.GetCurrentCloudURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := httpstate.SetDefaultOrg(cloudURL, defaultOrg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "problem logging in")
|
||||
|
@ -145,6 +162,8 @@ func newLoginCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&cloudURL, "cloud-url", "c", "", "A cloud URL to log in to")
|
||||
cmd.PersistentFlags().StringVar(&defaultOrg, "default-org", "", "A default org to associate with the login. "+
|
||||
"Please note, currently, only the managed and self-hosted backends support organizations")
|
||||
cmd.PersistentFlags().BoolVarP(&localMode, "local", "l", false, "Use Pulumi in local-only mode")
|
||||
|
||||
return cmd
|
||||
|
|
|
@ -172,7 +172,11 @@ func runNew(args newArgs) error {
|
|||
// created via the web app.
|
||||
var s backend.Stack
|
||||
if args.stack != "" && strings.Count(args.stack, "/") == 2 {
|
||||
existingStack, existingName, existingDesc, err := getStack(args.stack, opts)
|
||||
stackName, err := buildStackName(args.stack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existingStack, existingName, existingDesc, err := getStack(stackName, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -518,7 +522,11 @@ func promptAndCreateStack(prompt promptForValueFunc,
|
|||
}
|
||||
|
||||
if stack != "" {
|
||||
s, err := stackInit(b, stack, setCurrent, secretsProvider)
|
||||
stackName, err := buildStackName(stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := stackInit(b, stackName, setCurrent, secretsProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -536,7 +544,14 @@ func promptAndCreateStack(prompt promptForValueFunc,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := stackInit(b, stackName, setCurrent, secretsProvider)
|
||||
formattedStackName, err := buildStackName(stackName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := stackInit(b, formattedStackName, setCurrent, secretsProvider)
|
||||
if err != nil {
|
||||
if !yes {
|
||||
// Let the user know about the error and loop around to try again.
|
||||
|
|
153
pkg/cmd/pulumi/org.go
Normal file
153
pkg/cmd/pulumi/org.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/backend/display"
|
||||
"github.com/pulumi/pulumi/pkg/v3/backend/httpstate"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
||||
)
|
||||
|
||||
func newOrgCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "org",
|
||||
Short: "Manage Organization configuration",
|
||||
Long: "Manage Organization configuration.\n" +
|
||||
"\n" +
|
||||
"Use this command to manage organization configuration, " +
|
||||
"e.g. setting the default organization for a backend",
|
||||
Args: cmdutil.NoArgs,
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
cloudURL, err := workspace.GetCurrentCloudURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultOrg, err := workspace.GetBackendConfigDefaultOrg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Current Backend: %s\n", cloudURL)
|
||||
if defaultOrg != "" {
|
||||
fmt.Printf("Default Org: %s", defaultOrg)
|
||||
} else {
|
||||
fmt.Println("No Default Org Specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
|
||||
cmd.AddCommand(newOrgSetDefaultCmd())
|
||||
cmd.AddCommand(newOrgGetDefaultCmd())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newOrgSetDefaultCmd() *cobra.Command {
|
||||
var orgName string
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "set-default [NAME]",
|
||||
Args: cmdutil.ExactArgs(1),
|
||||
Short: "Set the default organization for the current backend",
|
||||
Long: "Set the default organization for the current backend.\n" +
|
||||
"\n" +
|
||||
"This command is used to set the default organization in which to create \n" +
|
||||
"projects and stacks for the current backend.\n" +
|
||||
"\n" +
|
||||
"Currently, only the managed and self-hosted backends support organizations. " +
|
||||
"If you try and set a default organization for a backend that does not \n" +
|
||||
"support create organizations, then an error will be returned by the CLI",
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
displayOpts := display.Options{
|
||||
Color: cmdutil.GetGlobalColorization(),
|
||||
}
|
||||
|
||||
orgName = args[0]
|
||||
|
||||
currentBe, err := currentBackend(displayOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !currentBe.SupportsOrganizations() {
|
||||
return fmt.Errorf("unable to set a default organization for backend type: %s",
|
||||
currentBe.Name())
|
||||
}
|
||||
|
||||
if _, ok := currentBe.(httpstate.Backend); ok {
|
||||
cloudURL, err := workspace.GetCurrentCloudURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := httpstate.SetDefaultOrg(cloudURL, orgName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newOrgGetDefaultCmd() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "get-default",
|
||||
Short: "Get the default org for the current backend",
|
||||
Long: "Get the default org for the current backend.\n" +
|
||||
"\n" +
|
||||
"This command is used to get the default organization for which and stacks are created in " +
|
||||
"the current backend.\n" +
|
||||
"\n" +
|
||||
"Currently, only the managed and self-hosted backends support organizations.",
|
||||
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
||||
displayOpts := display.Options{
|
||||
Color: cmdutil.GetGlobalColorization(),
|
||||
}
|
||||
|
||||
currentBe, err := currentBackend(displayOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !currentBe.SupportsOrganizations() {
|
||||
return fmt.Errorf("backends of this type %q do not support organizations",
|
||||
currentBe.Name())
|
||||
}
|
||||
|
||||
defaultOrg, err := workspace.GetBackendConfigDefaultOrg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if defaultOrg != "" {
|
||||
fmt.Println(defaultOrg)
|
||||
} else {
|
||||
fmt.Println("No Default Org Specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -210,6 +210,7 @@ func NewPulumiCmd() *cobra.Command {
|
|||
cmd.AddCommand(newConsoleCmd())
|
||||
cmd.AddCommand(newAboutCmd())
|
||||
cmd.AddCommand(newSchemaCmd())
|
||||
cmd.AddCommand(newOrgCmd())
|
||||
|
||||
// Less common, and thus hidden, commands:
|
||||
cmd.AddCommand(newGenCompletionCmd(cmd))
|
||||
|
|
|
@ -107,11 +107,15 @@ func newStackInitCmd() *cobra.Command {
|
|||
return errors.New("missing stack name")
|
||||
}
|
||||
|
||||
if err := b.ValidateStackName(stackName); err != nil {
|
||||
formattedStackName, err := buildStackName(stackName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.ValidateStackName(formattedStackName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stackRef, err := b.ParseStackReference(stackName)
|
||||
stackRef, err := b.ParseStackReference(formattedStackName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -862,3 +862,20 @@ func getRefreshOption(proj *workspace.Project, refresh string) (bool, error) {
|
|||
// the default functionality right now is to always skip a refresh
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func buildStackName(stackName string) (string, error) {
|
||||
if strings.Count(stackName, "/") == 2 {
|
||||
return stackName, nil
|
||||
}
|
||||
|
||||
defaultOrg, err := workspace.GetBackendConfigDefaultOrg()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if defaultOrg != "" {
|
||||
return fmt.Sprintf("%s/%s", defaultOrg, stackName), nil
|
||||
}
|
||||
|
||||
return stackName, nil
|
||||
}
|
||||
|
|
|
@ -114,6 +114,8 @@ require (
|
|||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
|
@ -130,6 +132,7 @@ require (
|
|||
github.com/pierrec/lz4 v2.6.0+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.6.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect
|
||||
|
|
|
@ -455,6 +455,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
|
||||
|
@ -580,11 +582,15 @@ github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w
|
|||
github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/ryboe/q v1.0.15 h1:atR2S58tRbVv5+t+Kx5qf+VvT2rpXYQPGAn5QtyB5jc=
|
||||
github.com/ryboe/q v1.0.15/go.mod h1:ecdh6eECsYWI/cWgtRaYjWb8fbz5YndR22B+xpAcHY8=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
|
||||
|
|
|
@ -251,3 +251,123 @@ func StoreCredentials(creds Credentials) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BackendConfig struct {
|
||||
DefaultOrg string `json:"defaultOrg,omitempty"` // The default org for this backend config.
|
||||
}
|
||||
|
||||
type PulumiConfig struct {
|
||||
BackendConfig map[string]BackendConfig `json:"backends,omitempty"` // a map of arbitrary backends configs.
|
||||
}
|
||||
|
||||
func getConfigFilePath() (string, error) {
|
||||
// Allow the folder we use to store config in to be overridden by tests
|
||||
pulumiFolder := os.Getenv(PulumiCredentialsPathEnvVar)
|
||||
if pulumiFolder == "" {
|
||||
folder, err := GetPulumiHomeDir()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to get the home path")
|
||||
}
|
||||
pulumiFolder = folder
|
||||
}
|
||||
|
||||
err := os.MkdirAll(pulumiFolder, 0700)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to create '%s'", pulumiFolder)
|
||||
}
|
||||
|
||||
return filepath.Join(pulumiFolder, "config.json"), nil
|
||||
}
|
||||
|
||||
func GetPulumiConfig() (PulumiConfig, error) {
|
||||
configFile, err := getConfigFilePath()
|
||||
if err != nil {
|
||||
return PulumiConfig{}, err
|
||||
}
|
||||
|
||||
c, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return PulumiConfig{}, nil
|
||||
}
|
||||
return PulumiConfig{}, errors.Wrapf(err, "reading '%s'", configFile)
|
||||
}
|
||||
|
||||
var config PulumiConfig
|
||||
if err = json.Unmarshal(c, &config); err != nil {
|
||||
return PulumiConfig{}, errors.Wrapf(err, "failed to read Pulumi config file")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func StorePulumiConfig(config PulumiConfig) error {
|
||||
configFile, err := getConfigFilePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "marshalling config object")
|
||||
}
|
||||
|
||||
// Use a temporary file and atomic os.Rename to ensure the file contents are
|
||||
// updated atomically to ensure concurrent `pulumi` CLI operations are safe.
|
||||
tempConfigFile, err := ioutil.TempFile(filepath.Dir(configFile), "config-*.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tempConfigFile.Write(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tempConfigFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Rename(tempConfigFile.Name(), configFile)
|
||||
if err != nil {
|
||||
contract.IgnoreError(os.Remove(tempConfigFile.Name()))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetBackendConfigDefaultOrg(backendURL, defaultOrg string) error {
|
||||
config, err := GetPulumiConfig()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.BackendConfig == nil {
|
||||
config.BackendConfig = make(map[string]BackendConfig)
|
||||
}
|
||||
|
||||
config.BackendConfig[backendURL] = BackendConfig{
|
||||
DefaultOrg: defaultOrg,
|
||||
}
|
||||
|
||||
return StorePulumiConfig(config)
|
||||
}
|
||||
|
||||
func GetBackendConfigDefaultOrg() (string, error) {
|
||||
config, err := GetPulumiConfig()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
backendURL, err := GetCurrentCloudURL()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if beConfig, ok := config.BackendConfig[backendURL]; ok {
|
||||
if beConfig.DefaultOrg != "" {
|
||||
return beConfig.DefaultOrg, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue