github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/logout.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 8 svchost "github.com/hashicorp/terraform-svchost" 9 "github.com/hashicorp/terraform/command/cliconfig" 10 "github.com/hashicorp/terraform/tfdiags" 11 ) 12 13 // LogoutCommand is a Command implementation which removes stored credentials 14 // for a remote service host. 15 type LogoutCommand struct { 16 Meta 17 } 18 19 // Run implements cli.Command. 20 func (c *LogoutCommand) Run(args []string) int { 21 args, err := c.Meta.process(args, false) 22 if err != nil { 23 return 1 24 } 25 26 cmdFlags := c.Meta.defaultFlagSet("logout") 27 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 28 if err := cmdFlags.Parse(args); err != nil { 29 return 1 30 } 31 32 args = cmdFlags.Args() 33 if len(args) > 1 { 34 c.Ui.Error( 35 "The logout command expects at most one argument: the host to log out of.") 36 cmdFlags.Usage() 37 return 1 38 } 39 40 var diags tfdiags.Diagnostics 41 42 givenHostname := "app.terraform.io" 43 if len(args) != 0 { 44 givenHostname = args[0] 45 } 46 47 hostname, err := svchost.ForComparison(givenHostname) 48 if err != nil { 49 diags = diags.Append(tfdiags.Sourceless( 50 tfdiags.Error, 51 "Invalid hostname", 52 fmt.Sprintf("The given hostname %q is not valid: %s.", givenHostname, err.Error()), 53 )) 54 c.showDiagnostics(diags) 55 return 1 56 } 57 58 // From now on, since we've validated the given hostname, we should use 59 // dispHostname in the UI to ensure we're presenting it in the canonical 60 // form, in case that helps users with debugging when things aren't 61 // working as expected. (Perhaps the normalization is part of the cause.) 62 dispHostname := hostname.ForDisplay() 63 64 creds := c.Services.CredentialsSource().(*cliconfig.CredentialsSource) 65 filename, _ := creds.CredentialsFilePath() 66 credsCtx := &loginCredentialsContext{ 67 Location: creds.HostCredentialsLocation(hostname), 68 LocalFilename: filename, // empty in the very unlikely event that we can't select a config directory for this user 69 HelperType: creds.CredentialsHelperType(), 70 } 71 72 if credsCtx.Location == cliconfig.CredentialsInOtherFile { 73 diags = diags.Append(tfdiags.Sourceless( 74 tfdiags.Error, 75 fmt.Sprintf("Credentials for %s are manually configured", dispHostname), 76 "The \"terraform logout\" command cannot log out because credentials for this host are manually configured in a CLI configuration file.\n\nTo log out, revoke the existing credentials and remove that block from the CLI configuration.", 77 )) 78 } 79 80 if diags.HasErrors() { 81 c.showDiagnostics(diags) 82 return 1 83 } 84 85 // credsCtx might not be set if we're using a mock credentials source 86 // in a test, but it should always be set in normal use. 87 if credsCtx != nil { 88 switch credsCtx.Location { 89 case cliconfig.CredentialsNotAvailable: 90 c.Ui.Output(fmt.Sprintf("No credentials for %s are stored.\n", dispHostname)) 91 return 0 92 case cliconfig.CredentialsViaHelper: 93 c.Ui.Output(fmt.Sprintf("Removing the stored credentials for %s from the configured\n%q credentials helper.\n", dispHostname, credsCtx.HelperType)) 94 case cliconfig.CredentialsInPrimaryFile: 95 c.Ui.Output(fmt.Sprintf("Removing the stored credentials for %s from the following file:\n %s\n", dispHostname, credsCtx.LocalFilename)) 96 } 97 } 98 99 err = creds.ForgetForHost(hostname) 100 if err != nil { 101 diags = diags.Append(tfdiags.Sourceless( 102 tfdiags.Error, 103 "Failed to remove API token", 104 fmt.Sprintf("Unable to remove stored API token: %s", err), 105 )) 106 } 107 108 c.showDiagnostics(diags) 109 if diags.HasErrors() { 110 return 1 111 } 112 113 c.Ui.Output( 114 fmt.Sprintf( 115 c.Colorize().Color(strings.TrimSpace(` 116 [green][bold]Success![reset] [bold]Terraform has removed the stored API token for %s.[reset] 117 `)), 118 dispHostname, 119 ) + "\n", 120 ) 121 122 return 0 123 } 124 125 // Help implements cli.Command. 126 func (c *LogoutCommand) Help() string { 127 defaultFile := c.defaultOutputFile() 128 if defaultFile == "" { 129 // Because this is just for the help message and it's very unlikely 130 // that a user wouldn't have a functioning home directory anyway, 131 // we'll just use a placeholder here. The real command has some 132 // more complex behavior for this case. This result is not correct 133 // on all platforms, but given how unlikely we are to hit this case 134 // that seems okay. 135 defaultFile = "~/.terraform/credentials.tfrc.json" 136 } 137 138 helpText := ` 139 Usage: terraform logout [hostname] 140 141 Removes locally-stored credentials for specified hostname. 142 143 Note: the API token is only removed from local storage, not destroyed on the 144 remote server, so it will remain valid until manually revoked. 145 146 If no hostname is provided, the default hostname is app.terraform.io. 147 %s 148 ` 149 return strings.TrimSpace(helpText) 150 } 151 152 // Synopsis implements cli.Command. 153 func (c *LogoutCommand) Synopsis() string { 154 return "Remove locally-stored credentials for a remote host" 155 } 156 157 func (c *LogoutCommand) defaultOutputFile() string { 158 if c.CLIConfigDir == "" { 159 return "" // no default available 160 } 161 return filepath.Join(c.CLIConfigDir, "credentials.tfrc.json") 162 }