-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6aadc93
commit d0bb77a
Showing
19 changed files
with
991 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package commands | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
|
||
"github.com/fatih/color" | ||
"github.com/spf13/cobra" | ||
"gopkg.in/yaml.v3" | ||
|
||
"github.com/platformsh/cli/internal/config" | ||
"github.com/platformsh/cli/internal/config/alt" | ||
) | ||
|
||
var configInstallCommand = &cobra.Command{ | ||
Use: "config:install [flags] [url]", | ||
Short: "Installs an alternative CLI, downloading new configuration from a URL", | ||
Args: cobra.ExactArgs(1), | ||
RunE: runConfigInstall, | ||
} | ||
|
||
func runConfigInstall(cmd *cobra.Command, args []string) error { | ||
cmd.PrintErrln("Downloading and validating new CLI configuration...") | ||
|
||
urlStr := args[0] | ||
if !strings.Contains(urlStr, "://") { | ||
urlStr = "https://" + urlStr | ||
} | ||
newCnfNode, newCnfStruct, err := alt.FetchConfig(cmd.Context(), urlStr) | ||
if err != nil { | ||
return err | ||
} | ||
cnf := config.FromContext(cmd.Context()) | ||
newExecutable := newCnfStruct.Application.Executable | ||
if newExecutable == cnf.Application.Executable { | ||
return fmt.Errorf("cannot install config for same executable name as this program: %s", newExecutable) | ||
} | ||
|
||
// Find the directory and file paths. | ||
executableDir, err := alt.FindBinDir() | ||
if err != nil { | ||
return err | ||
} | ||
configDir, err := alt.FindConfigDir() | ||
if err != nil { | ||
return err | ||
} | ||
configFilePath := filepath.Join(configDir, newExecutable+".yaml") | ||
executableFilePath := filepath.Join(executableDir, newExecutable) | ||
|
||
if path, err := exec.LookPath(newExecutable); err == nil && path != executableFilePath { | ||
return fmt.Errorf( | ||
"cannot install config: an executable with the same name already exists at another location: %s", | ||
path, | ||
) | ||
} | ||
|
||
// Generate the file content. | ||
executableContent := fmt.Sprintf( | ||
"#!/bin/sh\n"+ | ||
"# This file is automatically generated by the %s.\n"+ | ||
"export CLI_CONFIG_FILE=%s\n"+ | ||
`[ "$#" -gt 1 ] && shift # Skip first argument`+"\n"+ | ||
`%s "$@"`+"\n", | ||
cnf.Application.Name, | ||
configFilePath, | ||
cnf.Application.Executable, | ||
) | ||
configContent, err := yaml.Marshal(newCnfNode) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal new config: %w", err) | ||
} | ||
|
||
// Start writing the files. | ||
if err := os.MkdirAll(configDir, 0o755); err != nil { | ||
return err | ||
} | ||
if err := os.MkdirAll(executableDir, 0o755); err != nil { | ||
return err | ||
} | ||
if err := os.WriteFile(configFilePath, configContent, 0o600); err != nil { | ||
return err | ||
} | ||
if err := os.WriteFile(executableFilePath, []byte(executableContent), 0o700); err != nil { //nolint:gosec | ||
return err | ||
} | ||
|
||
// Make a formatter to replace the home directory with ~ in filenames. | ||
replaceHomeDir := func() func(string) string { | ||
hd, err := os.UserHomeDir() | ||
return func(p string) string { | ||
if err == nil && strings.HasPrefix(p, hd) { | ||
return "~" + strings.TrimPrefix(p, hd) | ||
} | ||
return p | ||
} | ||
}() | ||
|
||
cmd.PrintErrf( | ||
"\nThe following files have been created:\n"+ | ||
" - Configuration: %s\n"+ | ||
" - Executable: %s\n", | ||
color.CyanString(replaceHomeDir(configFilePath)), | ||
color.CyanString(replaceHomeDir(executableFilePath)), | ||
) | ||
|
||
if newCnfStruct.Updates.Check { | ||
cmd.PrintErrln("\nThe configuration file will be auto-updated periodically in the background.") | ||
} else { | ||
cmd.PrintErrln("\nEnable automatic updates to keep the configuration file up to date.") | ||
} | ||
|
||
isInPath, err := alt.InPath(executableDir) | ||
if err != nil { | ||
return fmt.Errorf("could not determine if the executable directory is in the PATH: %w", err) | ||
} | ||
|
||
envVarName := "PATH" | ||
if runtime.GOOS == "windows" { | ||
envVarName = "Path" | ||
} | ||
|
||
cmd.PrintErrln() | ||
|
||
if isInPath { | ||
cmd.PrintErrln("Run the new CLI with:", color.GreenString(filepath.Base(executableFilePath))) | ||
} else { | ||
cmd.PrintErrf( | ||
"Add the following directory to your %s: %s\n", | ||
envVarName, | ||
color.YellowString(replaceHomeDir(executableDir)), | ||
) | ||
cmd.PrintErrln() | ||
cmd.PrintErrln( | ||
"Then you will be able to run the new CLI with:", | ||
color.YellowString(filepath.Base(executableFilePath)), | ||
) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package commands | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"gopkg.in/yaml.v3" | ||
|
||
"github.com/platformsh/cli/internal/config" | ||
"github.com/platformsh/cli/internal/config/alt" | ||
) | ||
|
||
func TestConfigInstallCmd(t *testing.T) { | ||
tempDir := t.TempDir() | ||
|
||
// Ensure filesystem functions looking for UserHomeDir or UserConfigDir return the test directory. | ||
homeEnv := os.Getenv("HOME") | ||
require.NoError(t, os.Setenv("HOME", tempDir)) | ||
require.NoError(t, os.Unsetenv("XDG_CONFIG_HOME")) | ||
require.NoError(t, os.Unsetenv("TEST_HOME")) | ||
t.Cleanup(func() { | ||
_ = os.Setenv("HOME", homeEnv) | ||
}) | ||
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
if req.URL.Path == "/test-config.yaml" { | ||
cnf := testConfig() | ||
_ = yaml.NewEncoder(w).Encode(cnf) | ||
} | ||
})) | ||
defer server.Close() | ||
testConfigURL := server.URL + "/test-config.yaml" | ||
|
||
cnf := testConfig() | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
ctx = config.ToContext(ctx, cnf) | ||
|
||
cmd := configInstallCommand | ||
cmd.SetContext(ctx) | ||
cmd.SetOut(io.Discard) | ||
|
||
args := []string{testConfigURL} | ||
|
||
stdErrBuf := &bytes.Buffer{} | ||
cmd.SetErr(stdErrBuf) | ||
err := cmd.RunE(cmd, args) | ||
assert.ErrorContains(t, err, "cannot install config for same executable name as this program: test") | ||
|
||
cnf.Application.Executable = "test-cli-executable-host" | ||
err = cmd.RunE(cmd, args) | ||
assert.NoError(t, err) | ||
assert.FileExists(t, filepath.Join(tempDir, alt.HomeSubDir, "test-cli-executable.yaml")) | ||
assert.FileExists(t, filepath.Join(tempDir, alt.HomeSubDir, "bin", "test-cli-executable")) | ||
assert.Contains(t, stdErrBuf.String(), filepath.Join("~", alt.HomeSubDir, "test-cli-executable.yaml")) | ||
assert.Contains(t, stdErrBuf.String(), filepath.Join("~", alt.HomeSubDir, "bin", "test-cli-executable")) | ||
|
||
b, err := os.ReadFile(filepath.Join(tempDir, alt.HomeSubDir, "bin", "test-cli-executable")) | ||
require.NoError(t, err) | ||
assert.Contains(t, string(b), filepath.Join(tempDir, alt.HomeSubDir, "test-cli-executable.yaml")) | ||
assert.Contains(t, string(b), `test-cli-executable-host "$@"`) | ||
} | ||
|
||
func testConfig() *config.Config { | ||
cnf := &config.Config{} | ||
cnf.Application.Name = "Test CLI" | ||
cnf.Application.Executable = "test-cli-executable" // Not "test" as that is usually a real binary | ||
cnf.Application.EnvPrefix = "TEST_" | ||
cnf.Application.Slug = "test-cli" | ||
cnf.Application.UserConfigDir = ".test-cli" | ||
cnf.API.BaseURL = "https://localhost" | ||
cnf.API.AuthURL = "https://localhost" | ||
cnf.Detection.GitRemoteName = "platform" | ||
cnf.Service.Name = "Test" | ||
cnf.Service.EnvPrefix = "TEST_" | ||
cnf.Service.ProjectConfigDir = ".test" | ||
cnf.SSH.DomainWildcards = []string{"*"} | ||
return cnf | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.