From 2dd1436105045f2ce22fa2a961932e05797a3da9 Mon Sep 17 00:00:00 2001 From: Cirano Eusebi Date: Thu, 18 May 2023 18:06:27 -0300 Subject: [PATCH 01/14] Add tmp_mount to cli.yaml and to setup command. --- cmd/root.go | 2 ++ cmd/setup.go | 68 +++++++++++++++++++++++++++++++++++++----- cmd/setup_test.go | 46 ++++++++++++++++++++++++++++ cmd/setup_unit_test.go | 9 ++++++ go.mod | 4 +-- go.sum | 7 +++-- local/local.go | 12 ++++---- local/local_test.go | 4 +-- settings/settings.go | 1 + 9 files changed, 133 insertions(+), 20 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 05e028a99..8bee47878 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,6 +25,7 @@ import ( var defaultEndpoint = "graphql-unstable" var defaultHost = "https://circleci.com" var defaultRestEndpoint = "api/v2" +var defaultTmpMount = "/tmp" var trueString = "true" // rootCmd is used internally and global to the package but not exported @@ -113,6 +114,7 @@ func MakeCommands() *cobra.Command { RestEndpoint: defaultRestEndpoint, Endpoint: defaultEndpoint, GitHubAPI: "https://api.github.com/", + TmpMount: defaultTmpMount, } if err := rootOptions.Load(); err != nil { diff --git a/cmd/setup.go b/cmd/setup.go index efc30288a..240a3bc0e 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -16,10 +16,11 @@ type setupOptions struct { cfg *settings.Config cl *graphql.Client noPrompt bool - // Add host and token for use with --no-prompt - host string - token string - args []string + // Add host, tmpMount and token for use with --no-prompt + host string + token string + tmpMount string + args []string // This lets us pass in our own interface for testing tty setupUserInterface // Linked with --integration-testing flag for stubbing UI in gexec tests @@ -33,8 +34,10 @@ type setupOptions struct { type setupUserInterface interface { readTokenFromUser(message string) (string, error) readHostFromUser(message string, defaultValue string) string + readTmpMountFromUser(message string, defaultValue string) string askUserToConfirmEndpoint(message string) bool + askUserToConfirmTmpMount(message string) bool askUserToConfirmToken(message string) bool } @@ -46,6 +49,11 @@ func (setupInteractiveUI) readHostFromUser(message string, defaultValue string) return prompt.ReadStringFromUser(message, defaultValue) } +// readTmpMountFromUser implements the setupInteractiveUI interface for asking a user's input. +func (setupInteractiveUI) readTmpMountFromUser(message string, defaultValue string) string { + return prompt.ReadStringFromUser(message, defaultValue) +} + // readTokenFromUser implements the setupInteractiveUI interface for asking a user's token. func (setupInteractiveUI) readTokenFromUser(message string) (string, error) { return prompt.ReadSecretStringFromUser(message) @@ -55,6 +63,10 @@ func (setupInteractiveUI) askUserToConfirmEndpoint(message string) bool { return prompt.AskUserToConfirm(message) } +func (setupInteractiveUI) askUserToConfirmTmpMount(message string) bool { + return prompt.AskUserToConfirm(message) +} + func (setupInteractiveUI) askUserToConfirmToken(message string) bool { return prompt.AskUserToConfirm(message) } @@ -63,8 +75,10 @@ func (setupInteractiveUI) askUserToConfirmToken(message string) bool { type setupTestUI struct { host string token string + tmpMount string confirmEndpoint bool confirmToken bool + confirmTmpMount bool } // readHostFromUser implements the setupTestUI interface for asking a user's input. @@ -74,6 +88,13 @@ func (ui setupTestUI) readHostFromUser(message string, defaultValue string) stri return ui.host } +// readTmpMountFromUser implements the setupTestUI interface for asking a user's input. +// It works by simply printing the message to standard output and passing the input through. +func (ui setupTestUI) readTmpMountFromUser(message string, defaultValue string) string { + fmt.Println(message) + return ui.host +} + // readTokenFromUser implements the setupTestUI interface for asking a user's token. // It works by simply printing the message to standard output and passing the token through. func (ui setupTestUI) readTokenFromUser(message string) (string, error) { @@ -87,6 +108,12 @@ func (ui setupTestUI) askUserToConfirmEndpoint(message string) bool { return ui.confirmEndpoint } +// askUserToConfirmTmpMount works by printing the provided message to standard out and returning a Confirm dialogue up the chain. +func (ui setupTestUI) askUserToConfirmTmpMount(message string) bool { + fmt.Println(message) + return ui.confirmEndpoint +} + // askUserToConfirmToken works by printing the provided message to standard out and returning a Confirm dialogue up the chain. func (ui setupTestUI) askUserToConfirmToken(message string) bool { fmt.Println(message) @@ -111,6 +138,14 @@ func shouldAskForEndpoint(endpoint string, ui setupUserInterface, defaultValue s return ui.askUserToConfirmEndpoint(fmt.Sprintf("Do you want to reset the endpoint? (default: %s)", defaultValue)) } +func shouldAskForTmpMount(path string, ui setupUserInterface, defaultValue string) bool { + if path == defaultValue || path == "" { + return true + } + + return ui.askUserToConfirmTmpMount(fmt.Sprintf("A mount path is already set. Do you want to change it? (current: %s)", path)) +} + func newSetupCommand(config *settings.Config) *cobra.Command { opts := setupOptions{ cfg: config, @@ -135,8 +170,10 @@ func newSetupCommand(config *settings.Config) *cobra.Command { opts.tty = setupTestUI{ host: "boondoggle", token: "boondoggle", + tmpMount: "boondoggle", confirmEndpoint: true, confirmToken: true, + confirmTmpMount: true, } } @@ -165,6 +202,11 @@ func newSetupCommand(config *settings.Config) *cobra.Command { panic(err) } + setupCommand.Flags().StringVar(&opts.tmpMount, "tmpMount", "", "mountpath of the tmp folder of your docker engine") + if err := setupCommand.Flags().MarkHidden("tmpMount"); err != nil { + panic(err) + } + return setupCommand } @@ -186,6 +228,10 @@ func setup(opts setupOptions) error { opts.cfg.Endpoint = defaultEndpoint } + if shouldAskForTmpMount(opts.cfg.TmpMount, opts.tty, defaultTmpMount) { + opts.cfg.TmpMount = opts.tty.readTmpMountFromUser("Tmp mount", opts.cfg.TmpMount) + } + if err := opts.cfg.WriteToDisk(); err != nil { return errors.Wrap(err, "Failed to save config file") } @@ -243,7 +289,7 @@ func setupNoPrompt(opts setupOptions) error { return nil } - // Throw an error if both flags are blank are blank! + // Throw an error if host and token flags are blank if opts.host == "" && opts.token == "" { return errors.New("No existing host or token saved.\nThe proper format is `circleci setup --host HOST --token TOKEN --no-prompt") } @@ -258,8 +304,10 @@ func setupNoPrompt(opts setupOptions) error { // Use the default endpoint since we don't expose that to users config.Endpoint = defaultEndpoint config.RestEndpoint = defaultRestEndpoint - config.Host = opts.host // Set new host to flag - config.Token = opts.token // Set new token to flag + config.TmpMount = defaultTmpMount + config.Host = opts.host // Set new host to flag + config.Token = opts.token // Set new token to flag + config.TmpMount = opts.tmpMount // Set new tmpMount to flag // Reset their host if the flag was blank if opts.host == "" { @@ -273,6 +321,12 @@ func setupNoPrompt(opts setupOptions) error { config.Token = opts.cfg.Token } + // Reset their tmpPath if the flag was blank + if opts.tmpMount == "" { + fmt.Println("TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it.") + config.TmpMount = opts.cfg.TmpMount + } + // Then save the new config to disk if err := config.WriteToDisk(); err != nil { return errors.Wrap(err, "Failed to save config file") diff --git a/cmd/setup_test.go b/cmd/setup_test.go index 8ae09473c..802b7baa4 100644 --- a/cmd/setup_test.go +++ b/cmd/setup_test.go @@ -166,6 +166,7 @@ var _ = Describe("Setup without prompts", func() { tempSettings.Config.Write([]byte(` host: https://example.com token: fooBarBaz +tmp_mount: /tmp `)) }) @@ -195,6 +196,7 @@ token: fooBarBaz stdout := session.Wait().Out.Contents() Expect(string(stdout)).To(Equal(fmt.Sprintf(`Token unchanged from existing config. Use --token with --no-prompt to overwrite it. +TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it. Setup complete. Your configuration has been saved to %s. `, tempSettings.Config.Path))) @@ -221,6 +223,7 @@ token: fooBarBaz stdout := session.Wait().Out.Contents() Expect(string(stdout)).To(Equal(fmt.Sprintf(`Host unchanged from existing config. Use --host with --no-prompt to overwrite it. +TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it. Setup complete. Your configuration has been saved to %s. `, tempSettings.Config.Path))) @@ -268,6 +271,48 @@ token: asdf ) }) + It("write the configuration to a file", func() { + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + stdout := session.Wait().Out.Contents() + Expect(string(stdout)).To(Equal(fmt.Sprintf(`TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it. +Setup complete. +Your configuration has been saved to %s. +`, tempSettings.Config.Path))) + + Context("re-open the config to check the contents", func() { + file, err := os.Open(tempSettings.Config.Path) + Expect(err).ShouldNot(HaveOccurred()) + + reread, err := io.ReadAll(file) + Expect(err).ShouldNot(HaveOccurred()) + Expect(string(reread)).To(Equal(`host: https://zomg.com +endpoint: graphql-unstable +token: mytoken +rest_endpoint: api/v2 +tls_cert: "" +tls_insecure: false +orb_publishing: + default_namespace: "" + default_vcs_provider: "" + default_owner: "" +tmp_mount: /tmp +`)) + }) + }) + }) + Context("with all host, token and tmpMount flags", func() { + BeforeEach(func() { + command = commandWithHome(pathCLI, tempSettings.Home, + "setup", + "--host", "https://zomg.com", + "--token", "mytoken", + "--tmpMount", "/some/folder", + "--no-prompt", + "--skip-update-check", + ) + }) + It("write the configuration to a file", func() { session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) @@ -292,6 +337,7 @@ orb_publishing: default_namespace: "" default_vcs_provider: "" default_owner: "" +tmp_mount: /some/folder `)) }) }) diff --git a/cmd/setup_unit_test.go b/cmd/setup_unit_test.go index 8e5f9c873..c4896be49 100644 --- a/cmd/setup_unit_test.go +++ b/cmd/setup_unit_test.go @@ -110,6 +110,7 @@ var _ = Describe("Setup with prompts", func() { BeforeEach(func() { opts.cfg.Host = "https://example.com/graphql" opts.cfg.Token = token + opts.cfg.TmpMount = defaultTmpMount }) It("should print setup complete", func() { @@ -118,6 +119,7 @@ var _ = Describe("Setup with prompts", func() { token: token, confirmEndpoint: true, confirmToken: true, + confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -131,6 +133,7 @@ API token has been set. CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) +Tmp mount Setup complete. Your configuration has been saved to %s. @@ -150,6 +153,7 @@ token: %s token: token, confirmEndpoint: true, confirmToken: false, + confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -161,6 +165,7 @@ token: %s CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) +Tmp mount Setup complete. Your configuration has been saved to %s. @@ -228,6 +233,7 @@ token: %s token: token, confirmEndpoint: true, confirmToken: true, + confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -240,6 +246,7 @@ API token has been set. CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) +Tmp mount Setup complete. Your configuration has been saved to %s. @@ -303,6 +310,7 @@ Trying to query our API for your profile name... Hello, %s. token: token, confirmEndpoint: true, confirmToken: true, + confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -315,6 +323,7 @@ API token has been set. CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) +Tmp mount Setup complete. Your configuration has been saved to %s. diff --git a/go.mod b/go.mod index 2374ce2b9..e51f9d30b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/uuid v1.3.0 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/mitchellh/mapstructure v1.4.1 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo v1.16.4 @@ -80,7 +80,7 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect diff --git a/go.sum b/go.sum index 00cb5ea45..5d4558aa0 100644 --- a/go.sum +++ b/go.sum @@ -281,13 +281,14 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= diff --git a/local/local.go b/local/local.go index 4aa60ae72..c97af3532 100644 --- a/local/local.go +++ b/local/local.go @@ -50,7 +50,7 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config, args []string) error { return fmt.Errorf("config errors %v", configResponse.Errors) } - processedConfigPath, err := writeStringToTempFile(configResponse.OutputYaml) + processedConfigPath, err := writeStringToTempFile(cfg.TmpMount, configResponse.OutputYaml) // The file at processedConfigPath must be left in place until after the call // to `docker run` has completed. Typically, we would `defer` a call to remove @@ -83,7 +83,7 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config, args []string) error { job := args[0] dockerSocketPath, _ := flags.GetString("docker-socket-path") - arguments := generateDockerCommand(processedConfigPath, image, pwd, job, dockerSocketPath, processedArgs...) + arguments := generateDockerCommand(cfg.TmpMount, processedConfigPath, image, pwd, job, dockerSocketPath, processedArgs...) if cfg.Debug { _, err = fmt.Fprintf(os.Stderr, "Starting docker with args: %s", arguments) @@ -199,14 +199,14 @@ func ensureDockerIsAvailable() (string, error) { } // Write data to a temp file, and return the path to that file. -func writeStringToTempFile(data string) (string, error) { +func writeStringToTempFile(tmpMount, data string) (string, error) { // It's important to specify `/tmp` here as the location of the temp file. // On macOS, the regular temp directories is not shared with Docker by default. // The error message is along the lines of: // > The path /var/folders/q0/2g2lcf6j79df6vxqm0cg_0zm0000gn/T/287575618-config.yml // > is not shared from OS X and is not known to Docker. // Docker has `/tmp` shared by default. - f, err := os.CreateTemp("/tmp", "*_circleci_config.yml") + f, err := os.CreateTemp(tmpMount, "*_circleci_config.yml") if err != nil { return "", errors.Wrap(err, "Error creating temporary config file") @@ -219,8 +219,8 @@ func writeStringToTempFile(data string) (string, error) { return f.Name(), nil } -func generateDockerCommand(configPath, image, pwd string, job string, dockerSocketPath string, arguments ...string) []string { - const configPathInsideContainer = "/tmp/local_build_config.yml" +func generateDockerCommand(tmpMount, configPath, image, pwd string, job string, dockerSocketPath string, arguments ...string) []string { + configPathInsideContainer := fmt.Sprintf("%s/local_build_config.yml", tmpMount) core := []string{"docker", "run", "--interactive", "--tty", "--rm", "--volume", fmt.Sprintf("%s:/var/run/docker.sock", dockerSocketPath), "--volume", fmt.Sprintf("%s:%s", configPath, configPathInsideContainer), diff --git a/local/local_test.go b/local/local_test.go index 5a290f722..c83046f58 100644 --- a/local/local_test.go +++ b/local/local_test.go @@ -17,7 +17,7 @@ var _ = Describe("build", func() { It("can generate a command line", func() { home, err := os.UserHomeDir() Expect(err).NotTo(HaveOccurred()) - Expect(generateDockerCommand("/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( + Expect(generateDockerCommand("/tmp", "/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( "docker", "run", "--interactive", @@ -36,7 +36,7 @@ var _ = Describe("build", func() { }) It("can write temp files", func() { - path, err := writeStringToTempFile("cynosure") + path, err := writeStringToTempFile("/tmp", "cynosure") Expect(err).NotTo(HaveOccurred()) defer os.Remove(path) Expect(os.ReadFile(path)).To(BeEquivalentTo("cynosure")) diff --git a/settings/settings.go b/settings/settings.go index 162235543..9a716203d 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -49,6 +49,7 @@ type Config struct { // The value of this field is the path where the telemetry will be written MockTelemetry string `yaml:"-"` OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"` + TmpMount string `yaml:"tmp_mount"` } type OrbPublishingInfo struct { From 156870c2a39d5215b105bebe0c2c10e37342db3d Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Mon, 17 Jul 2023 18:01:10 +0200 Subject: [PATCH 02/14] remove tmp mount from setup --- cmd/setup.go | 66 ++++-------------------------------------- cmd/setup_test.go | 48 +----------------------------- cmd/setup_unit_test.go | 9 ------ 3 files changed, 7 insertions(+), 116 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index 240a3bc0e..bda0843f5 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -16,11 +16,10 @@ type setupOptions struct { cfg *settings.Config cl *graphql.Client noPrompt bool - // Add host, tmpMount and token for use with --no-prompt - host string - token string - tmpMount string - args []string + // Add host and token for use with --no-prompt + host string + token string + args []string // This lets us pass in our own interface for testing tty setupUserInterface // Linked with --integration-testing flag for stubbing UI in gexec tests @@ -34,10 +33,8 @@ type setupOptions struct { type setupUserInterface interface { readTokenFromUser(message string) (string, error) readHostFromUser(message string, defaultValue string) string - readTmpMountFromUser(message string, defaultValue string) string askUserToConfirmEndpoint(message string) bool - askUserToConfirmTmpMount(message string) bool askUserToConfirmToken(message string) bool } @@ -49,11 +46,6 @@ func (setupInteractiveUI) readHostFromUser(message string, defaultValue string) return prompt.ReadStringFromUser(message, defaultValue) } -// readTmpMountFromUser implements the setupInteractiveUI interface for asking a user's input. -func (setupInteractiveUI) readTmpMountFromUser(message string, defaultValue string) string { - return prompt.ReadStringFromUser(message, defaultValue) -} - // readTokenFromUser implements the setupInteractiveUI interface for asking a user's token. func (setupInteractiveUI) readTokenFromUser(message string) (string, error) { return prompt.ReadSecretStringFromUser(message) @@ -63,10 +55,6 @@ func (setupInteractiveUI) askUserToConfirmEndpoint(message string) bool { return prompt.AskUserToConfirm(message) } -func (setupInteractiveUI) askUserToConfirmTmpMount(message string) bool { - return prompt.AskUserToConfirm(message) -} - func (setupInteractiveUI) askUserToConfirmToken(message string) bool { return prompt.AskUserToConfirm(message) } @@ -75,10 +63,8 @@ func (setupInteractiveUI) askUserToConfirmToken(message string) bool { type setupTestUI struct { host string token string - tmpMount string confirmEndpoint bool confirmToken bool - confirmTmpMount bool } // readHostFromUser implements the setupTestUI interface for asking a user's input. @@ -88,13 +74,6 @@ func (ui setupTestUI) readHostFromUser(message string, defaultValue string) stri return ui.host } -// readTmpMountFromUser implements the setupTestUI interface for asking a user's input. -// It works by simply printing the message to standard output and passing the input through. -func (ui setupTestUI) readTmpMountFromUser(message string, defaultValue string) string { - fmt.Println(message) - return ui.host -} - // readTokenFromUser implements the setupTestUI interface for asking a user's token. // It works by simply printing the message to standard output and passing the token through. func (ui setupTestUI) readTokenFromUser(message string) (string, error) { @@ -108,12 +87,6 @@ func (ui setupTestUI) askUserToConfirmEndpoint(message string) bool { return ui.confirmEndpoint } -// askUserToConfirmTmpMount works by printing the provided message to standard out and returning a Confirm dialogue up the chain. -func (ui setupTestUI) askUserToConfirmTmpMount(message string) bool { - fmt.Println(message) - return ui.confirmEndpoint -} - // askUserToConfirmToken works by printing the provided message to standard out and returning a Confirm dialogue up the chain. func (ui setupTestUI) askUserToConfirmToken(message string) bool { fmt.Println(message) @@ -138,14 +111,6 @@ func shouldAskForEndpoint(endpoint string, ui setupUserInterface, defaultValue s return ui.askUserToConfirmEndpoint(fmt.Sprintf("Do you want to reset the endpoint? (default: %s)", defaultValue)) } -func shouldAskForTmpMount(path string, ui setupUserInterface, defaultValue string) bool { - if path == defaultValue || path == "" { - return true - } - - return ui.askUserToConfirmTmpMount(fmt.Sprintf("A mount path is already set. Do you want to change it? (current: %s)", path)) -} - func newSetupCommand(config *settings.Config) *cobra.Command { opts := setupOptions{ cfg: config, @@ -170,10 +135,8 @@ func newSetupCommand(config *settings.Config) *cobra.Command { opts.tty = setupTestUI{ host: "boondoggle", token: "boondoggle", - tmpMount: "boondoggle", confirmEndpoint: true, confirmToken: true, - confirmTmpMount: true, } } @@ -202,11 +165,6 @@ func newSetupCommand(config *settings.Config) *cobra.Command { panic(err) } - setupCommand.Flags().StringVar(&opts.tmpMount, "tmpMount", "", "mountpath of the tmp folder of your docker engine") - if err := setupCommand.Flags().MarkHidden("tmpMount"); err != nil { - panic(err) - } - return setupCommand } @@ -228,10 +186,6 @@ func setup(opts setupOptions) error { opts.cfg.Endpoint = defaultEndpoint } - if shouldAskForTmpMount(opts.cfg.TmpMount, opts.tty, defaultTmpMount) { - opts.cfg.TmpMount = opts.tty.readTmpMountFromUser("Tmp mount", opts.cfg.TmpMount) - } - if err := opts.cfg.WriteToDisk(); err != nil { return errors.Wrap(err, "Failed to save config file") } @@ -304,10 +258,8 @@ func setupNoPrompt(opts setupOptions) error { // Use the default endpoint since we don't expose that to users config.Endpoint = defaultEndpoint config.RestEndpoint = defaultRestEndpoint - config.TmpMount = defaultTmpMount - config.Host = opts.host // Set new host to flag - config.Token = opts.token // Set new token to flag - config.TmpMount = opts.tmpMount // Set new tmpMount to flag + config.Host = opts.host // Set new host to flag + config.Token = opts.token // Set new token to flag // Reset their host if the flag was blank if opts.host == "" { @@ -321,12 +273,6 @@ func setupNoPrompt(opts setupOptions) error { config.Token = opts.cfg.Token } - // Reset their tmpPath if the flag was blank - if opts.tmpMount == "" { - fmt.Println("TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it.") - config.TmpMount = opts.cfg.TmpMount - } - // Then save the new config to disk if err := config.WriteToDisk(); err != nil { return errors.Wrap(err, "Failed to save config file") diff --git a/cmd/setup_test.go b/cmd/setup_test.go index 802b7baa4..d1cdb4e11 100644 --- a/cmd/setup_test.go +++ b/cmd/setup_test.go @@ -166,7 +166,6 @@ var _ = Describe("Setup without prompts", func() { tempSettings.Config.Write([]byte(` host: https://example.com token: fooBarBaz -tmp_mount: /tmp `)) }) @@ -196,7 +195,6 @@ token: fooBarBaz stdout := session.Wait().Out.Contents() Expect(string(stdout)).To(Equal(fmt.Sprintf(`Token unchanged from existing config. Use --token with --no-prompt to overwrite it. -TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it. Setup complete. Your configuration has been saved to %s. `, tempSettings.Config.Path))) @@ -223,7 +221,6 @@ token: fooBarBaz stdout := session.Wait().Out.Contents() Expect(string(stdout)).To(Equal(fmt.Sprintf(`Host unchanged from existing config. Use --host with --no-prompt to overwrite it. -TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it. Setup complete. Your configuration has been saved to %s. `, tempSettings.Config.Path))) @@ -271,49 +268,7 @@ token: asdf ) }) - It("write the configuration to a file", func() { - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) - stdout := session.Wait().Out.Contents() - Expect(string(stdout)).To(Equal(fmt.Sprintf(`TmpMount unchanged from existing config. Use --tmpMount with --no-prompt to overwrite it. -Setup complete. -Your configuration has been saved to %s. -`, tempSettings.Config.Path))) - - Context("re-open the config to check the contents", func() { - file, err := os.Open(tempSettings.Config.Path) - Expect(err).ShouldNot(HaveOccurred()) - - reread, err := io.ReadAll(file) - Expect(err).ShouldNot(HaveOccurred()) - Expect(string(reread)).To(Equal(`host: https://zomg.com -endpoint: graphql-unstable -token: mytoken -rest_endpoint: api/v2 -tls_cert: "" -tls_insecure: false -orb_publishing: - default_namespace: "" - default_vcs_provider: "" - default_owner: "" -tmp_mount: /tmp -`)) - }) - }) - }) - Context("with all host, token and tmpMount flags", func() { - BeforeEach(func() { - command = commandWithHome(pathCLI, tempSettings.Home, - "setup", - "--host", "https://zomg.com", - "--token", "mytoken", - "--tmpMount", "/some/folder", - "--no-prompt", - "--skip-update-check", - ) - }) - - It("write the configuration to a file", func() { + It("writes the configuration to a file", func() { session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) stdout := session.Wait().Out.Contents() @@ -337,7 +292,6 @@ orb_publishing: default_namespace: "" default_vcs_provider: "" default_owner: "" -tmp_mount: /some/folder `)) }) }) diff --git a/cmd/setup_unit_test.go b/cmd/setup_unit_test.go index c4896be49..8e5f9c873 100644 --- a/cmd/setup_unit_test.go +++ b/cmd/setup_unit_test.go @@ -110,7 +110,6 @@ var _ = Describe("Setup with prompts", func() { BeforeEach(func() { opts.cfg.Host = "https://example.com/graphql" opts.cfg.Token = token - opts.cfg.TmpMount = defaultTmpMount }) It("should print setup complete", func() { @@ -119,7 +118,6 @@ var _ = Describe("Setup with prompts", func() { token: token, confirmEndpoint: true, confirmToken: true, - confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -133,7 +131,6 @@ API token has been set. CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) -Tmp mount Setup complete. Your configuration has been saved to %s. @@ -153,7 +150,6 @@ token: %s token: token, confirmEndpoint: true, confirmToken: false, - confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -165,7 +161,6 @@ token: %s CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) -Tmp mount Setup complete. Your configuration has been saved to %s. @@ -233,7 +228,6 @@ token: %s token: token, confirmEndpoint: true, confirmToken: true, - confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -246,7 +240,6 @@ API token has been set. CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) -Tmp mount Setup complete. Your configuration has been saved to %s. @@ -310,7 +303,6 @@ Trying to query our API for your profile name... Hello, %s. token: token, confirmEndpoint: true, confirmToken: true, - confirmTmpMount: true, } output := clitest.WithCapturedOutput(func() { @@ -323,7 +315,6 @@ API token has been set. CircleCI Host CircleCI host has been set. Do you want to reset the endpoint? (default: graphql-unstable) -Tmp mount Setup complete. Your configuration has been saved to %s. From 50384fa0e28d2ea14d12e808fb9cf0cb5ca3d316 Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Tue, 18 Jul 2023 10:52:06 +0200 Subject: [PATCH 03/14] add temp dir to local execute command and --- cmd/root.go | 2 -- local/local.go | 50 ++++++++++++++++++++++++++------------------ local/local_test.go | 6 +++--- settings/settings.go | 37 ++++++++++++++------------------ 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 8bee47878..05e028a99 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,7 +25,6 @@ import ( var defaultEndpoint = "graphql-unstable" var defaultHost = "https://circleci.com" var defaultRestEndpoint = "api/v2" -var defaultTmpMount = "/tmp" var trueString = "true" // rootCmd is used internally and global to the package but not exported @@ -114,7 +113,6 @@ func MakeCommands() *cobra.Command { RestEndpoint: defaultRestEndpoint, Endpoint: defaultEndpoint, GitHubAPI: "https://api.github.com/", - TmpMount: defaultTmpMount, } if err := rootOptions.Load(); err != nil { diff --git a/local/local.go b/local/local.go index c97af3532..a82b4db91 100644 --- a/local/local.go +++ b/local/local.go @@ -24,6 +24,18 @@ const DefaultDockerSocketPath = "/var/run/docker.sock" func Execute(flags *pflag.FlagSet, cfg *settings.Config, args []string) error { var err error var configResponse *config.ConfigResponse + + // Get temp dir from flags + tempDir, _ := flags.GetString("temp-dir") + if tempDir == "" { + // If not specified, get from config + tempDir = cfg.TempDir + if tempDir == "" { + // If not specified, use system default + tempDir = os.TempDir() + } + } + processedArgs, configPath := buildAgentArguments(flags) compiler, err := config.NewWithConfig(cfg) @@ -50,14 +62,14 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config, args []string) error { return fmt.Errorf("config errors %v", configResponse.Errors) } - processedConfigPath, err := writeStringToTempFile(cfg.TmpMount, configResponse.OutputYaml) + processedConfigPath, err := writeStringToTempFile(tempDir, configResponse.OutputYaml) // The file at processedConfigPath must be left in place until after the call // to `docker run` has completed. Typically, we would `defer` a call to remove // the file. In this case, we execute `docker` using `syscall.Exec`, which // replaces the current process, and no more go code will execute at that // point, so we cannot delete the file easily. We choose to leave the file - // in-place in /tmp. + // in-place in the temporary directory. if err != nil { return err @@ -83,7 +95,7 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config, args []string) error { job := args[0] dockerSocketPath, _ := flags.GetString("docker-socket-path") - arguments := generateDockerCommand(cfg.TmpMount, processedConfigPath, image, pwd, job, dockerSocketPath, processedArgs...) + arguments := generateDockerCommand(tempDir, processedConfigPath, image, pwd, job, dockerSocketPath, processedArgs...) if cfg.Debug { _, err = fmt.Fprintf(os.Stderr, "Starting docker with args: %s", arguments) @@ -107,17 +119,18 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config, args []string) error { // They don't reflect the entire structure or available flags, only those which // are public in the original command. func AddFlagsForDocumentation(flags *pflag.FlagSet) { + flags.String("temp-dir", "", "path to local directory to store temporary config files") flags.StringP("config", "c", DefaultConfigPath, "config file") flags.Int("node-total", 1, "total number of parallel nodes") flags.Int("index", 0, "node index of parallelism") flags.Bool("skip-checkout", true, "use local path as-is") - flags.StringArrayP("volume", "v", nil, "Volume bind-mounting") - flags.String("checkout-key", "~/.ssh/id_rsa", "Git Checkout key") - flags.String("revision", "", "Git Revision") - flags.String("branch", "", "Git branch") - flags.String("repo-url", "", "Git Url") - flags.StringArrayP("env", "e", nil, "Set environment variables, e.g. `-e VAR=VAL`") - flags.String("docker-socket-path", DefaultDockerSocketPath, "Path to the host's docker socket") + flags.StringArrayP("volume", "v", nil, "volume bind-mounting") + flags.String("checkout-key", "~/.ssh/id_rsa", "git checkout key") + flags.String("revision", "", "git revision") + flags.String("branch", "", "git branch") + flags.String("repo-url", "", "git URL") + flags.StringArrayP("env", "e", nil, "set environment variables, e.g. `-e VAR=VAL`") + flags.String("docker-socket-path", DefaultDockerSocketPath, "path to the host's docker socket") } // Given the full set of flags that were passed to this command, return the path @@ -199,14 +212,11 @@ func ensureDockerIsAvailable() (string, error) { } // Write data to a temp file, and return the path to that file. -func writeStringToTempFile(tmpMount, data string) (string, error) { - // It's important to specify `/tmp` here as the location of the temp file. - // On macOS, the regular temp directories is not shared with Docker by default. - // The error message is along the lines of: - // > The path /var/folders/q0/2g2lcf6j79df6vxqm0cg_0zm0000gn/T/287575618-config.yml - // > is not shared from OS X and is not known to Docker. - // Docker has `/tmp` shared by default. - f, err := os.CreateTemp(tmpMount, "*_circleci_config.yml") +func writeStringToTempFile(tempDir, data string) (string, error) { + if tempDir == "" { + tempDir = os.TempDir() + } + f, err := os.CreateTemp(tempDir, "*_circleci_config.yml") if err != nil { return "", errors.Wrap(err, "Error creating temporary config file") @@ -219,8 +229,8 @@ func writeStringToTempFile(tmpMount, data string) (string, error) { return f.Name(), nil } -func generateDockerCommand(tmpMount, configPath, image, pwd string, job string, dockerSocketPath string, arguments ...string) []string { - configPathInsideContainer := fmt.Sprintf("%s/local_build_config.yml", tmpMount) +func generateDockerCommand(tempDir, configPath, image, pwd string, job string, dockerSocketPath string, arguments ...string) []string { + configPathInsideContainer := fmt.Sprintf("%s/local_build_config.yml", tempDir) core := []string{"docker", "run", "--interactive", "--tty", "--rm", "--volume", fmt.Sprintf("%s:/var/run/docker.sock", dockerSocketPath), "--volume", fmt.Sprintf("%s:%s", configPath, configPathInsideContainer), diff --git a/local/local_test.go b/local/local_test.go index c83046f58..847c4354c 100644 --- a/local/local_test.go +++ b/local/local_test.go @@ -17,19 +17,19 @@ var _ = Describe("build", func() { It("can generate a command line", func() { home, err := os.UserHomeDir() Expect(err).NotTo(HaveOccurred()) - Expect(generateDockerCommand("/tmp", "/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( + Expect(generateDockerCommand("/tempdir", "/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( "docker", "run", "--interactive", "--tty", "--rm", "--volume", "/var/run/docker.sock:/var/run/docker.sock", - "--volume", "/config/path:/tmp/local_build_config.yml", + "--volume", "/config/path:/tempdir/local_build_config.yml", "--volume", "/current/directory:/current/directory", "--volume", home+"/.circleci:/root/.circleci", "--workdir", "/current/directory", "docker-image-name", "circleci", "build", - "--config", "/tmp/local_build_config.yml", + "--config", "/tempdir/local_build_config.yml", "--job", "build", "extra-1", "extra-2", )) diff --git a/settings/settings.go b/settings/settings.go index 9a716203d..ef46f829e 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -29,27 +29,22 @@ var ( // Config is used to represent the current state of a CLI instance. type Config struct { - Host string `yaml:"host"` - DlHost string `yaml:"-"` - Endpoint string `yaml:"endpoint"` - Token string `yaml:"token"` - RestEndpoint string `yaml:"rest_endpoint"` - TLSCert string `yaml:"tls_cert"` - TLSInsecure bool `yaml:"tls_insecure"` - HTTPClient *http.Client `yaml:"-"` - Data *data.DataBag `yaml:"-"` - Debug bool `yaml:"-"` - Address string `yaml:"-"` - FileUsed string `yaml:"-"` - GitHubAPI string `yaml:"-"` - SkipUpdateCheck bool `yaml:"-"` - // Parameter used to disable telemetry from tests - IsTelemetryDisabled bool `yaml:"-"` - // If this value is defined, the telemetry will write all its events a file - // The value of this field is the path where the telemetry will be written - MockTelemetry string `yaml:"-"` - OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"` - TmpMount string `yaml:"tmp_mount"` + Host string `yaml:"host"` + DlHost string `yaml:"-"` + Endpoint string `yaml:"endpoint"` + Token string `yaml:"token"` + RestEndpoint string `yaml:"rest_endpoint"` + TLSCert string `yaml:"tls_cert"` + TLSInsecure bool `yaml:"tls_insecure"` + HTTPClient *http.Client `yaml:"-"` + Data *data.DataBag `yaml:"-"` + Debug bool `yaml:"-"` + Address string `yaml:"-"` + FileUsed string `yaml:"-"` + GitHubAPI string `yaml:"-"` + SkipUpdateCheck bool `yaml:"-"` + OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"` + TempDir string `yaml:"temp_dir"` } type OrbPublishingInfo struct { From 1a8e7987003e8a4f857570f6e831c6472d96a14b Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Tue, 18 Jul 2023 12:16:18 +0200 Subject: [PATCH 04/14] fix flags --- cmd/build.go | 1 + local/local.go | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/build.go b/cmd/build.go index df4d110c4..3def64ab6 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -33,6 +33,7 @@ func newLocalExecuteCommand(config *settings.Config) *cobra.Command { buildCommand.Flags().String("build-agent-version", "", `The version of the build agent image you want to use. This can be configured by writing in $HOME/.circleci/build_agent_settings.json: '{"LatestSha256":""}'`) buildCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org") buildCommand.Flags().String("org-id", "", "organization id, used when a config depends on private orbs belonging to that org") + buildCommand.Flags().String("temp-dir", "", "path to local directory to store temporary config files") return buildCommand } diff --git a/local/local.go b/local/local.go index a82b4db91..b9d9780fd 100644 --- a/local/local.go +++ b/local/local.go @@ -119,7 +119,6 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config, args []string) error { // They don't reflect the entire structure or available flags, only those which // are public in the original command. func AddFlagsForDocumentation(flags *pflag.FlagSet) { - flags.String("temp-dir", "", "path to local directory to store temporary config files") flags.StringP("config", "c", DefaultConfigPath, "config file") flags.Int("node-total", 1, "total number of parallel nodes") flags.Int("index", 0, "node index of parallelism") @@ -148,7 +147,13 @@ func buildAgentArguments(flags *pflag.FlagSet) ([]string, string) { // build a list of all supplied flags, that we will pass on to build-agent flags.Visit(func(flag *pflag.Flag) { - if flag.Name != "build-agent-version" && flag.Name != "org-slug" && flag.Name != "config" && flag.Name != "debug" && flag.Name != "org-id" && flag.Name != "docker-socket-path" { + if flag.Name != "build-agent-version" && + flag.Name != "org-slug" && + flag.Name != "config" && + flag.Name != "temp-dir" && + flag.Name != "debug" && + flag.Name != "org-id" && + flag.Name != "docker-socket-path" { result = append(result, unparseFlag(flags, flag)...) } }) From accf9eb236647410e0d296c9c11f700eb83f3b4c Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Tue, 18 Jul 2023 17:32:10 +0200 Subject: [PATCH 05/14] e2e test for docker execute --- local/local.go | 2 +- local/local_suite_test.go | 28 ++++++ local/local_test.go | 174 +++++++++++++------------------------- local/local_unit_test.go | 124 +++++++++++++++++++++++++++ 4 files changed, 214 insertions(+), 114 deletions(-) create mode 100644 local/local_unit_test.go diff --git a/local/local.go b/local/local.go index b9d9780fd..3922f8b7d 100644 --- a/local/local.go +++ b/local/local.go @@ -236,7 +236,7 @@ func writeStringToTempFile(tempDir, data string) (string, error) { func generateDockerCommand(tempDir, configPath, image, pwd string, job string, dockerSocketPath string, arguments ...string) []string { configPathInsideContainer := fmt.Sprintf("%s/local_build_config.yml", tempDir) - core := []string{"docker", "run", "--interactive", "--tty", "--rm", + core := []string{"docker", "run", "--rm", "--volume", fmt.Sprintf("%s:/var/run/docker.sock", dockerSocketPath), "--volume", fmt.Sprintf("%s:%s", configPath, configPathInsideContainer), "--volume", fmt.Sprintf("%s:%s", pwd, pwd), diff --git a/local/local_suite_test.go b/local/local_suite_test.go index 3f31554cc..ec59b1034 100644 --- a/local/local_suite_test.go +++ b/local/local_suite_test.go @@ -1,13 +1,41 @@ package local_test import ( + "fmt" + "os" + "os/exec" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" ) +var pathCLI string + +var _ = BeforeSuite(func() { + var err error + pathCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli") + Expect(err).ShouldNot(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + gexec.CleanupBuildArtifacts() +}) + func TestLocal(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Local Suite") } + +func commandWithHome(bin, home string, args ...string) *exec.Cmd { + command := exec.Command(bin, args...) + + command.Env = append( + os.Environ(), + fmt.Sprintf("HOME=%s", home), + fmt.Sprintf("USERPROFILE=%s", home), // windows + ) + + return command +} diff --git a/local/local_test.go b/local/local_test.go index 847c4354c..f5b47ebc8 100644 --- a/local/local_test.go +++ b/local/local_test.go @@ -1,126 +1,74 @@ -package local +package local_test import ( - "io" "os" + "os/exec" + "path/filepath" + "time" . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - "github.com/spf13/pflag" + "github.com/onsi/gomega/gexec" ) -var _ = Describe("build", func() { - - Describe("invoking docker", func() { - - It("can generate a command line", func() { - home, err := os.UserHomeDir() - Expect(err).NotTo(HaveOccurred()) - Expect(generateDockerCommand("/tempdir", "/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( - "docker", - "run", - "--interactive", - "--tty", - "--rm", - "--volume", "/var/run/docker.sock:/var/run/docker.sock", - "--volume", "/config/path:/tempdir/local_build_config.yml", - "--volume", "/current/directory:/current/directory", - "--volume", home+"/.circleci:/root/.circleci", - "--workdir", "/current/directory", - "docker-image-name", "circleci", "build", - "--config", "/tempdir/local_build_config.yml", - "--job", "build", - "extra-1", "extra-2", - )) - }) - - It("can write temp files", func() { - path, err := writeStringToTempFile("/tmp", "cynosure") - Expect(err).NotTo(HaveOccurred()) - defer os.Remove(path) - Expect(os.ReadFile(path)).To(BeEquivalentTo("cynosure")) - }) +type executeSettings struct { + projectDir string + config *os.File +} + +func newExecuteSettings() *executeSettings { + projectDir, err := os.MkdirTemp("", "circleci-cli-test-project") + Expect(err).ToNot(HaveOccurred()) + + circleCIPath := filepath.Join(projectDir, ".circleci") + Expect(os.Mkdir(circleCIPath, 0700)).To(Succeed()) + + configFilePath := filepath.Join(circleCIPath, "config.yml") + configFile, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_RDWR, 0600) + Expect(err).ToNot(HaveOccurred()) + + return &executeSettings{ + projectDir: projectDir, + config: configFile, + } +} + +func (es *executeSettings) close() { + es.config.Close() + os.RemoveAll(es.projectDir) +} + +var _ = Describe("Execute integration tests", func() { + var ( + execSettings *executeSettings + command *exec.Cmd + ) + + BeforeEach(func() { + execSettings = newExecuteSettings() + _, err := execSettings.config.Write( + []byte(`version: 2.1 +jobs: + build: + docker: + - image: cimg/base:2023.03 + steps: + - run: echo "hello world"`, + ), + ) + Expect(err).ShouldNot(HaveOccurred()) + + command = exec.Command(pathCLI, "local", "execute", "build") + command.Dir = execSettings.projectDir }) - Describe("argument parsing", func() { - - makeFlags := func(args []string) (*pflag.FlagSet, error) { - flags := pflag.NewFlagSet("test", pflag.ContinueOnError) - AddFlagsForDocumentation(flags) - // add a 'debug' flag - the build command will inherit this from the - // root command when not testing in isolation. - flags.Bool("debug", false, "Enable debug logging.") - flags.SetOutput(io.Discard) - err := flags.Parse(args) - return flags, err - } - - type TestCase struct { - input []string - expectedArgs []string - expectedConfigPath string - expectedError string - } - - DescribeTable("extracting config", func(testCase TestCase) { - flags, err := makeFlags(testCase.input) - if testCase.expectedError != "" { - Expect(err).To(MatchError(testCase.expectedError)) - } - args, configPath := buildAgentArguments(flags) - Expect(args).To(Equal(testCase.expectedArgs)) - Expect(configPath).To(Equal(testCase.expectedConfigPath)) - - }, - Entry("no args", TestCase{ - input: []string{}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{}, - }), - - Entry("single letter", TestCase{ - input: []string{"-c", "b"}, - expectedConfigPath: "b", - expectedArgs: []string{}, - }), - - Entry("asking for help", TestCase{ - input: []string{"-h", "b"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{}, - expectedError: "pflag: help requested", - }), - - Entry("many args", TestCase{ - input: []string{"--config", "foo", "--index", "9", "d"}, - expectedConfigPath: "foo", - expectedArgs: []string{"--index", "9", "d"}, - }), - - Entry("many args, multiple envs", TestCase{ - input: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, - }), - - Entry("many args, multiple volumes (issue #469)", TestCase{ - input: []string{"-v", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{"--volume", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, - }), - - Entry("comma in env value (issue #440)", TestCase{ - input: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, - }), - - Entry("args that are not flags", TestCase{ - input: []string{"a", "--debug", "b", "--config", "foo", "d"}, - expectedConfigPath: "foo", - expectedArgs: []string{"a", "b", "d"}, - })) + AfterEach(func() { + execSettings.close() + }) + It("should run a local job", func() { + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session, time.Minute).Should(gexec.Exit(0)) }) }) diff --git a/local/local_unit_test.go b/local/local_unit_test.go new file mode 100644 index 000000000..9a5ef2b0e --- /dev/null +++ b/local/local_unit_test.go @@ -0,0 +1,124 @@ +package local + +import ( + "io" + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + "github.com/spf13/pflag" +) + +var _ = Describe("build", func() { + + Describe("invoking docker", func() { + + It("can generate a command line", func() { + home, err := os.UserHomeDir() + Expect(err).NotTo(HaveOccurred()) + Expect(generateDockerCommand("/tempdir", "/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( + "docker", + "run", + "--rm", + "--volume", "/var/run/docker.sock:/var/run/docker.sock", + "--volume", "/config/path:/tempdir/local_build_config.yml", + "--volume", "/current/directory:/current/directory", + "--volume", home+"/.circleci:/root/.circleci", + "--workdir", "/current/directory", + "docker-image-name", "circleci", "build", + "--config", "/tempdir/local_build_config.yml", + "--job", "build", + "extra-1", "extra-2", + )) + }) + + It("can write temp files", func() { + path, err := writeStringToTempFile("/tmp", "cynosure") + Expect(err).NotTo(HaveOccurred()) + defer os.Remove(path) + Expect(os.ReadFile(path)).To(BeEquivalentTo("cynosure")) + }) + }) + + Describe("argument parsing", func() { + + makeFlags := func(args []string) (*pflag.FlagSet, error) { + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) + AddFlagsForDocumentation(flags) + // add a 'debug' flag - the build command will inherit this from the + // root command when not testing in isolation. + flags.Bool("debug", false, "Enable debug logging.") + flags.SetOutput(io.Discard) + err := flags.Parse(args) + return flags, err + } + + type TestCase struct { + input []string + expectedArgs []string + expectedConfigPath string + expectedError string + } + + DescribeTable("extracting config", func(testCase TestCase) { + flags, err := makeFlags(testCase.input) + if testCase.expectedError != "" { + Expect(err).To(MatchError(testCase.expectedError)) + } + args, configPath := buildAgentArguments(flags) + Expect(args).To(Equal(testCase.expectedArgs)) + Expect(configPath).To(Equal(testCase.expectedConfigPath)) + + }, + Entry("no args", TestCase{ + input: []string{}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{}, + }), + + Entry("single letter", TestCase{ + input: []string{"-c", "b"}, + expectedConfigPath: "b", + expectedArgs: []string{}, + }), + + Entry("asking for help", TestCase{ + input: []string{"-h", "b"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{}, + expectedError: "pflag: help requested", + }), + + Entry("many args", TestCase{ + input: []string{"--config", "foo", "--index", "9", "d"}, + expectedConfigPath: "foo", + expectedArgs: []string{"--index", "9", "d"}, + }), + + Entry("many args, multiple envs", TestCase{ + input: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, + }), + + Entry("many args, multiple volumes (issue #469)", TestCase{ + input: []string{"-v", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{"--volume", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, + }), + + Entry("comma in env value (issue #440)", TestCase{ + input: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, + }), + + Entry("args that are not flags", TestCase{ + input: []string{"a", "--debug", "b", "--config", "foo", "d"}, + expectedConfigPath: "foo", + expectedArgs: []string{"a", "b", "d"}, + })) + + }) +}) From 3be85373c95d76a43522450dfdde26472ef8bdde Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Tue, 18 Jul 2023 18:56:20 +0200 Subject: [PATCH 06/14] add test cases --- local/local_suite_test.go | 15 --------- local/local_test.go | 70 ++++++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/local/local_suite_test.go b/local/local_suite_test.go index ec59b1034..2e8e02924 100644 --- a/local/local_suite_test.go +++ b/local/local_suite_test.go @@ -1,9 +1,6 @@ package local_test import ( - "fmt" - "os" - "os/exec" "testing" . "github.com/onsi/ginkgo" @@ -27,15 +24,3 @@ func TestLocal(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Local Suite") } - -func commandWithHome(bin, home string, args ...string) *exec.Cmd { - command := exec.Command(bin, args...) - - command.Env = append( - os.Environ(), - fmt.Sprintf("HOME=%s", home), - fmt.Sprintf("USERPROFILE=%s", home), // windows - ) - - return command -} diff --git a/local/local_test.go b/local/local_test.go index f5b47ebc8..90eb30dce 100644 --- a/local/local_test.go +++ b/local/local_test.go @@ -1,11 +1,13 @@ package local_test import ( + "fmt" "os" "os/exec" "path/filepath" "time" + "github.com/CircleCI-Public/circleci-cli/clitest" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -39,27 +41,20 @@ func (es *executeSettings) close() { } var _ = Describe("Execute integration tests", func() { - var ( - execSettings *executeSettings - command *exec.Cmd - ) - - BeforeEach(func() { - execSettings = newExecuteSettings() - _, err := execSettings.config.Write( - []byte(`version: 2.1 + const configData = `version: 2.1 jobs: build: docker: - image: cimg/base:2023.03 steps: - - run: echo "hello world"`, - ), - ) - Expect(err).ShouldNot(HaveOccurred()) + - run: echo "hello world"` - command = exec.Command(pathCLI, "local", "execute", "build") - command.Dir = execSettings.projectDir + var execSettings *executeSettings + + BeforeEach(func() { + execSettings = newExecuteSettings() + _, err := execSettings.config.Write([]byte(configData)) + Expect(err).ShouldNot(HaveOccurred()) }) AfterEach(func() { @@ -67,8 +62,53 @@ jobs: }) It("should run a local job", func() { + command := exec.Command(pathCLI, "local", "execute", "build") + command.Dir = execSettings.projectDir + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) Eventually(session, time.Minute).Should(gexec.Exit(0)) }) + + It("should run a local job with a custom temp dir in flags", func() { + tempDir, err := os.MkdirTemp("", "circleci-cli-test-tmp") + Expect(err).ShouldNot(HaveOccurred()) + + command := exec.Command(pathCLI, "local", "execute", "--temp-dir", tempDir, "build") + command.Dir = execSettings.projectDir + + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session, time.Minute).Should(gexec.Exit(0)) + + tempConfigData, err := os.ReadFile(execSettings.config.Name()) + Expect(err).ShouldNot(HaveOccurred()) + Expect(string(tempConfigData)).To(Equal(configData)) + }) + + It("should run a local job with a custom temp dir in settings", func() { + tempDir, err := os.MkdirTemp("", "circleci-cli-test-tmp") + Expect(err).ShouldNot(HaveOccurred()) + + tempSettings := clitest.WithTempSettings() + defer tempSettings.Close() + + tempSettings.Config.Write([]byte(fmt.Sprintf("temp_dir: '%s'", tempDir))) + + command := exec.Command(pathCLI, "local", "execute", "build") + command.Dir = execSettings.projectDir + command.Env = append( + os.Environ(), + fmt.Sprintf("HOME=%s", tempSettings.Home), + fmt.Sprintf("USERPROFILE=%s", tempSettings.Home), // windows + ) + + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session, time.Minute).Should(gexec.Exit(0)) + + tempConfigData, err := os.ReadFile(execSettings.config.Name()) + Expect(err).ShouldNot(HaveOccurred()) + Expect(string(tempConfigData)).To(Equal(configData)) + }) }) From b6c138edab8c401db34e336d6c2fd50e40564454 Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Wed, 19 Jul 2023 09:44:06 +0200 Subject: [PATCH 07/14] setup remote docker in test jobs --- .circleci/config.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a1b5456c..7153242d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,6 +83,7 @@ jobs: name: windows/default shell: bash --login -eo pipefail steps: + - setup_remote_docker - run: git config --global core.autocrlf false - checkout - run: mkdir test_results @@ -102,6 +103,7 @@ jobs: test_mac: executor: mac steps: + - setup_remote_docker - checkout - run: | brew update @@ -123,6 +125,7 @@ jobs: docker: - image: cimg/ruby:2.7 steps: + - setup_remote_docker - checkout - attach_workspace: at: . @@ -144,6 +147,7 @@ jobs: test: executor: go steps: + - setup_remote_docker - checkout - gomod - run: make test @@ -153,6 +157,7 @@ jobs: environment: CGO_ENABLED: 1 steps: + - setup_remote_docker - checkout - force-http-1 - gomod From b9e1e9f353cd8eff9123572c1363a8bd382ed122 Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Wed, 19 Jul 2023 10:13:32 +0200 Subject: [PATCH 08/14] use --mount instead of --volume for docker bind mounts --- local/local.go | 8 ++++---- local/local_unit_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/local/local.go b/local/local.go index 3922f8b7d..041357d8e 100644 --- a/local/local.go +++ b/local/local.go @@ -237,10 +237,10 @@ func writeStringToTempFile(tempDir, data string) (string, error) { func generateDockerCommand(tempDir, configPath, image, pwd string, job string, dockerSocketPath string, arguments ...string) []string { configPathInsideContainer := fmt.Sprintf("%s/local_build_config.yml", tempDir) core := []string{"docker", "run", "--rm", - "--volume", fmt.Sprintf("%s:/var/run/docker.sock", dockerSocketPath), - "--volume", fmt.Sprintf("%s:%s", configPath, configPathInsideContainer), - "--volume", fmt.Sprintf("%s:%s", pwd, pwd), - "--volume", fmt.Sprintf("%s:/root/.circleci", settings.SettingsPath()), + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/var/run/docker.sock", dockerSocketPath), + "--mount", fmt.Sprintf("type=bind,src=%s,dst=%s", configPath, configPathInsideContainer), + "--mount", fmt.Sprintf("type=bind,src=%s,dst=%s", pwd, pwd), + "--mount", fmt.Sprintf("type=bind,src=%s,dst=/root/.circleci", settings.SettingsPath()), "--workdir", pwd, image, "circleci", "build", "--config", configPathInsideContainer, "--job", job} return append(core, arguments...) diff --git a/local/local_unit_test.go b/local/local_unit_test.go index 9a5ef2b0e..7f30972b5 100644 --- a/local/local_unit_test.go +++ b/local/local_unit_test.go @@ -21,10 +21,10 @@ var _ = Describe("build", func() { "docker", "run", "--rm", - "--volume", "/var/run/docker.sock:/var/run/docker.sock", - "--volume", "/config/path:/tempdir/local_build_config.yml", - "--volume", "/current/directory:/current/directory", - "--volume", home+"/.circleci:/root/.circleci", + "--mount", "type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock", + "--mount", "type=bind,src=/config/path,dst=/tempdir/local_build_config.yml", + "--mount", "type=bind,src=/current/directory,dst=/current/directory", + "--mount", "type=bind,src="+home+"/.circleci,dst=/root/.circleci", "--workdir", "/current/directory", "docker-image-name", "circleci", "build", "--config", "/tempdir/local_build_config.yml", From 0367621dfb9d523b4475c1c9a61510ed7424ffe2 Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Mon, 4 Dec 2023 17:37:39 +0100 Subject: [PATCH 09/14] fix rebase mistake --- settings/settings.go | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/settings/settings.go b/settings/settings.go index ef46f829e..e28d4b302 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -29,22 +29,27 @@ var ( // Config is used to represent the current state of a CLI instance. type Config struct { - Host string `yaml:"host"` - DlHost string `yaml:"-"` - Endpoint string `yaml:"endpoint"` - Token string `yaml:"token"` - RestEndpoint string `yaml:"rest_endpoint"` - TLSCert string `yaml:"tls_cert"` - TLSInsecure bool `yaml:"tls_insecure"` - HTTPClient *http.Client `yaml:"-"` - Data *data.DataBag `yaml:"-"` - Debug bool `yaml:"-"` - Address string `yaml:"-"` - FileUsed string `yaml:"-"` - GitHubAPI string `yaml:"-"` - SkipUpdateCheck bool `yaml:"-"` - OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"` - TempDir string `yaml:"temp_dir"` + Host string `yaml:"host"` + DlHost string `yaml:"-"` + Endpoint string `yaml:"endpoint"` + Token string `yaml:"token"` + RestEndpoint string `yaml:"rest_endpoint"` + TLSCert string `yaml:"tls_cert"` + TLSInsecure bool `yaml:"tls_insecure"` + HTTPClient *http.Client `yaml:"-"` + Data *data.DataBag `yaml:"-"` + Debug bool `yaml:"-"` + Address string `yaml:"-"` + FileUsed string `yaml:"-"` + GitHubAPI string `yaml:"-"` + SkipUpdateCheck bool `yaml:"-"` + // Parameter used to disable telemetry from tests + IsTelemetryDisabled bool `yaml:"-"` + // If this value is defined, the telemetry will write all its events a file + // The value of this field is the path where the telemetry will be written + MockTelemetry string `yaml:"-"` + OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"` + TempDir string `yaml:"temp_dir"` } type OrbPublishingInfo struct { From 417a385c5a5695c8e68582de7cdc68a897a0776a Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Mon, 4 Dec 2023 17:37:58 +0100 Subject: [PATCH 10/14] remove remote docker from non-linux test jobs --- .circleci/config.yml | 54 +++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7153242d4..f3d3359ad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,7 +83,6 @@ jobs: name: windows/default shell: bash --login -eo pipefail steps: - - setup_remote_docker - run: git config --global core.autocrlf false - checkout - run: mkdir test_results @@ -103,7 +102,6 @@ jobs: test_mac: executor: mac steps: - - setup_remote_docker - checkout - run: | brew update @@ -317,38 +315,38 @@ jobs: - run: name: Setup Scanning command: | - git config --global url."https://$GITHUB_USER:$GITHUB_TOKEN@github.com/circleci/".insteadOf "https://github.com/circleci/" + git config --global url."https://$GITHUB_USER:$GITHUB_TOKEN@github.com/circleci/".insteadOf "https://github.com/circleci/" - when: condition: - or: - - equal: [ main, << pipeline.git.branch >> ] + or: + - equal: [main, << pipeline.git.branch >>] steps: - - run: - name: Launching Snyk Orb Scanning - command: echo "Running snyk/scan on main; uploading the results" - - run: - name: Cleanup RemoteRepoURL - command: echo 'export REMOTE_REPO_URL="${CIRCLE_REPOSITORY_URL%".git"}"' >> "$BASH_ENV" - - snyk/scan: - organization: "circleci-public" - fail-on-issues: true - severity-threshold: high - monitor-on-build: true - additional-arguments: "--all-projects --remote-repo-url=${REMOTE_REPO_URL} -d" + - run: + name: Launching Snyk Orb Scanning + command: echo "Running snyk/scan on main; uploading the results" + - run: + name: Cleanup RemoteRepoURL + command: echo 'export REMOTE_REPO_URL="${CIRCLE_REPOSITORY_URL%".git"}"' >> "$BASH_ENV" + - snyk/scan: + organization: "circleci-public" + fail-on-issues: true + severity-threshold: high + monitor-on-build: true + additional-arguments: "--all-projects --remote-repo-url=${REMOTE_REPO_URL} -d" - unless: condition: - or: - - equal: [ main, << pipeline.git.branch >> ] + or: + - equal: [main, << pipeline.git.branch >>] steps: - - run: - name: Launching Snyk Orb Scanning - command: echo "Running snyk/scan on branch; not uploading the results" - - snyk/scan: - organization: "circleci-public" - fail-on-issues: true - severity-threshold: high - monitor-on-build: false - additional-arguments: "--all-projects -d" + - run: + name: Launching Snyk Orb Scanning + command: echo "Running snyk/scan on branch; not uploading the results" + - snyk/scan: + organization: "circleci-public" + fail-on-issues: true + severity-threshold: high + monitor-on-build: false + additional-arguments: "--all-projects -d" workflows: ci: From 29be1b5794fdc2b29e1cea307fd0cffa7809320a Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Mon, 4 Dec 2023 17:56:12 +0100 Subject: [PATCH 11/14] skip local execute tests on non-linux OS --- local/local_suite_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/local/local_suite_test.go b/local/local_suite_test.go index 2e8e02924..1046abcba 100644 --- a/local/local_suite_test.go +++ b/local/local_suite_test.go @@ -1,6 +1,7 @@ package local_test import ( + "runtime" "testing" . "github.com/onsi/ginkgo" @@ -21,6 +22,9 @@ var _ = AfterSuite(func() { }) func TestLocal(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("Skipping test on non-linux OS") + } RegisterFailHandler(Fail) RunSpecs(t, "Local Suite") } From ded01ca9d7ba790fe47ffad8f22d38515926110f Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Mon, 4 Dec 2023 18:04:11 +0100 Subject: [PATCH 12/14] omit empty temp dir --- settings/settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/settings.go b/settings/settings.go index e28d4b302..76237d989 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -49,7 +49,7 @@ type Config struct { // The value of this field is the path where the telemetry will be written MockTelemetry string `yaml:"-"` OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"` - TempDir string `yaml:"temp_dir"` + TempDir string `yaml:"temp_dir,omitempty"` } type OrbPublishingInfo struct { From 61cb34eebceaf86a30ca9a91116159bf9426415c Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Tue, 5 Dec 2023 10:28:52 +0100 Subject: [PATCH 13/14] remove local execute e2e tests --- local/local_suite_test.go | 17 --- local/local_test.go | 212 ++++++++++++++++++++------------------ local/local_unit_test.go | 124 ---------------------- 3 files changed, 111 insertions(+), 242 deletions(-) delete mode 100644 local/local_unit_test.go diff --git a/local/local_suite_test.go b/local/local_suite_test.go index 1046abcba..3f31554cc 100644 --- a/local/local_suite_test.go +++ b/local/local_suite_test.go @@ -1,30 +1,13 @@ package local_test import ( - "runtime" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" ) -var pathCLI string - -var _ = BeforeSuite(func() { - var err error - pathCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli") - Expect(err).ShouldNot(HaveOccurred()) -}) - -var _ = AfterSuite(func() { - gexec.CleanupBuildArtifacts() -}) - func TestLocal(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Skipping test on non-linux OS") - } RegisterFailHandler(Fail) RunSpecs(t, "Local Suite") } diff --git a/local/local_test.go b/local/local_test.go index 90eb30dce..7f30972b5 100644 --- a/local/local_test.go +++ b/local/local_test.go @@ -1,114 +1,124 @@ -package local_test +package local import ( - "fmt" + "io" "os" - "os/exec" - "path/filepath" - "time" - "github.com/CircleCI-Public/circleci-cli/clitest" . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" + "github.com/spf13/pflag" ) -type executeSettings struct { - projectDir string - config *os.File -} - -func newExecuteSettings() *executeSettings { - projectDir, err := os.MkdirTemp("", "circleci-cli-test-project") - Expect(err).ToNot(HaveOccurred()) - - circleCIPath := filepath.Join(projectDir, ".circleci") - Expect(os.Mkdir(circleCIPath, 0700)).To(Succeed()) - - configFilePath := filepath.Join(circleCIPath, "config.yml") - configFile, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_RDWR, 0600) - Expect(err).ToNot(HaveOccurred()) - - return &executeSettings{ - projectDir: projectDir, - config: configFile, - } -} - -func (es *executeSettings) close() { - es.config.Close() - os.RemoveAll(es.projectDir) -} - -var _ = Describe("Execute integration tests", func() { - const configData = `version: 2.1 -jobs: - build: - docker: - - image: cimg/base:2023.03 - steps: - - run: echo "hello world"` - - var execSettings *executeSettings - - BeforeEach(func() { - execSettings = newExecuteSettings() - _, err := execSettings.config.Write([]byte(configData)) - Expect(err).ShouldNot(HaveOccurred()) +var _ = Describe("build", func() { + + Describe("invoking docker", func() { + + It("can generate a command line", func() { + home, err := os.UserHomeDir() + Expect(err).NotTo(HaveOccurred()) + Expect(generateDockerCommand("/tempdir", "/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( + "docker", + "run", + "--rm", + "--mount", "type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock", + "--mount", "type=bind,src=/config/path,dst=/tempdir/local_build_config.yml", + "--mount", "type=bind,src=/current/directory,dst=/current/directory", + "--mount", "type=bind,src="+home+"/.circleci,dst=/root/.circleci", + "--workdir", "/current/directory", + "docker-image-name", "circleci", "build", + "--config", "/tempdir/local_build_config.yml", + "--job", "build", + "extra-1", "extra-2", + )) + }) + + It("can write temp files", func() { + path, err := writeStringToTempFile("/tmp", "cynosure") + Expect(err).NotTo(HaveOccurred()) + defer os.Remove(path) + Expect(os.ReadFile(path)).To(BeEquivalentTo("cynosure")) + }) }) - AfterEach(func() { - execSettings.close() - }) - - It("should run a local job", func() { - command := exec.Command(pathCLI, "local", "execute", "build") - command.Dir = execSettings.projectDir - - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session, time.Minute).Should(gexec.Exit(0)) - }) - - It("should run a local job with a custom temp dir in flags", func() { - tempDir, err := os.MkdirTemp("", "circleci-cli-test-tmp") - Expect(err).ShouldNot(HaveOccurred()) - - command := exec.Command(pathCLI, "local", "execute", "--temp-dir", tempDir, "build") - command.Dir = execSettings.projectDir - - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session, time.Minute).Should(gexec.Exit(0)) - - tempConfigData, err := os.ReadFile(execSettings.config.Name()) - Expect(err).ShouldNot(HaveOccurred()) - Expect(string(tempConfigData)).To(Equal(configData)) - }) - - It("should run a local job with a custom temp dir in settings", func() { - tempDir, err := os.MkdirTemp("", "circleci-cli-test-tmp") - Expect(err).ShouldNot(HaveOccurred()) - - tempSettings := clitest.WithTempSettings() - defer tempSettings.Close() - - tempSettings.Config.Write([]byte(fmt.Sprintf("temp_dir: '%s'", tempDir))) - - command := exec.Command(pathCLI, "local", "execute", "build") - command.Dir = execSettings.projectDir - command.Env = append( - os.Environ(), - fmt.Sprintf("HOME=%s", tempSettings.Home), - fmt.Sprintf("USERPROFILE=%s", tempSettings.Home), // windows - ) - - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session, time.Minute).Should(gexec.Exit(0)) + Describe("argument parsing", func() { + + makeFlags := func(args []string) (*pflag.FlagSet, error) { + flags := pflag.NewFlagSet("test", pflag.ContinueOnError) + AddFlagsForDocumentation(flags) + // add a 'debug' flag - the build command will inherit this from the + // root command when not testing in isolation. + flags.Bool("debug", false, "Enable debug logging.") + flags.SetOutput(io.Discard) + err := flags.Parse(args) + return flags, err + } + + type TestCase struct { + input []string + expectedArgs []string + expectedConfigPath string + expectedError string + } + + DescribeTable("extracting config", func(testCase TestCase) { + flags, err := makeFlags(testCase.input) + if testCase.expectedError != "" { + Expect(err).To(MatchError(testCase.expectedError)) + } + args, configPath := buildAgentArguments(flags) + Expect(args).To(Equal(testCase.expectedArgs)) + Expect(configPath).To(Equal(testCase.expectedConfigPath)) + + }, + Entry("no args", TestCase{ + input: []string{}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{}, + }), + + Entry("single letter", TestCase{ + input: []string{"-c", "b"}, + expectedConfigPath: "b", + expectedArgs: []string{}, + }), + + Entry("asking for help", TestCase{ + input: []string{"-h", "b"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{}, + expectedError: "pflag: help requested", + }), + + Entry("many args", TestCase{ + input: []string{"--config", "foo", "--index", "9", "d"}, + expectedConfigPath: "foo", + expectedArgs: []string{"--index", "9", "d"}, + }), + + Entry("many args, multiple envs", TestCase{ + input: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, + }), + + Entry("many args, multiple volumes (issue #469)", TestCase{ + input: []string{"-v", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{"--volume", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, + }), + + Entry("comma in env value (issue #440)", TestCase{ + input: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, + expectedConfigPath: ".circleci/config.yml", + expectedArgs: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, + }), + + Entry("args that are not flags", TestCase{ + input: []string{"a", "--debug", "b", "--config", "foo", "d"}, + expectedConfigPath: "foo", + expectedArgs: []string{"a", "b", "d"}, + })) - tempConfigData, err := os.ReadFile(execSettings.config.Name()) - Expect(err).ShouldNot(HaveOccurred()) - Expect(string(tempConfigData)).To(Equal(configData)) }) }) diff --git a/local/local_unit_test.go b/local/local_unit_test.go deleted file mode 100644 index 7f30972b5..000000000 --- a/local/local_unit_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package local - -import ( - "io" - "os" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "github.com/spf13/pflag" -) - -var _ = Describe("build", func() { - - Describe("invoking docker", func() { - - It("can generate a command line", func() { - home, err := os.UserHomeDir() - Expect(err).NotTo(HaveOccurred()) - Expect(generateDockerCommand("/tempdir", "/config/path", "docker-image-name", "/current/directory", "build", "/var/run/docker.sock", "extra-1", "extra-2")).To(ConsistOf( - "docker", - "run", - "--rm", - "--mount", "type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock", - "--mount", "type=bind,src=/config/path,dst=/tempdir/local_build_config.yml", - "--mount", "type=bind,src=/current/directory,dst=/current/directory", - "--mount", "type=bind,src="+home+"/.circleci,dst=/root/.circleci", - "--workdir", "/current/directory", - "docker-image-name", "circleci", "build", - "--config", "/tempdir/local_build_config.yml", - "--job", "build", - "extra-1", "extra-2", - )) - }) - - It("can write temp files", func() { - path, err := writeStringToTempFile("/tmp", "cynosure") - Expect(err).NotTo(HaveOccurred()) - defer os.Remove(path) - Expect(os.ReadFile(path)).To(BeEquivalentTo("cynosure")) - }) - }) - - Describe("argument parsing", func() { - - makeFlags := func(args []string) (*pflag.FlagSet, error) { - flags := pflag.NewFlagSet("test", pflag.ContinueOnError) - AddFlagsForDocumentation(flags) - // add a 'debug' flag - the build command will inherit this from the - // root command when not testing in isolation. - flags.Bool("debug", false, "Enable debug logging.") - flags.SetOutput(io.Discard) - err := flags.Parse(args) - return flags, err - } - - type TestCase struct { - input []string - expectedArgs []string - expectedConfigPath string - expectedError string - } - - DescribeTable("extracting config", func(testCase TestCase) { - flags, err := makeFlags(testCase.input) - if testCase.expectedError != "" { - Expect(err).To(MatchError(testCase.expectedError)) - } - args, configPath := buildAgentArguments(flags) - Expect(args).To(Equal(testCase.expectedArgs)) - Expect(configPath).To(Equal(testCase.expectedConfigPath)) - - }, - Entry("no args", TestCase{ - input: []string{}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{}, - }), - - Entry("single letter", TestCase{ - input: []string{"-c", "b"}, - expectedConfigPath: "b", - expectedArgs: []string{}, - }), - - Entry("asking for help", TestCase{ - input: []string{"-h", "b"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{}, - expectedError: "pflag: help requested", - }), - - Entry("many args", TestCase{ - input: []string{"--config", "foo", "--index", "9", "d"}, - expectedConfigPath: "foo", - expectedArgs: []string{"--index", "9", "d"}, - }), - - Entry("many args, multiple envs", TestCase{ - input: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{"--env", "foo", "--env", "bar", "--env", "baz"}, - }), - - Entry("many args, multiple volumes (issue #469)", TestCase{ - input: []string{"-v", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{"--volume", "/foo:/bar", "--volume", "/bin:/baz", "--volume", "/boo:/bop"}, - }), - - Entry("comma in env value (issue #440)", TestCase{ - input: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, - expectedConfigPath: ".circleci/config.yml", - expectedArgs: []string{"--env", "{\"json\":[\"like\",\"value\"]}"}, - }), - - Entry("args that are not flags", TestCase{ - input: []string{"a", "--debug", "b", "--config", "foo", "d"}, - expectedConfigPath: "foo", - expectedArgs: []string{"a", "b", "d"}, - })) - - }) -}) From fd78de8ead357957e5f6b31791b8b051acd36cc4 Mon Sep 17 00:00:00 2001 From: Charles Francoise Date: Tue, 5 Dec 2023 12:00:54 +0100 Subject: [PATCH 14/14] remove unused setup_remote_docker --- .circleci/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f3d3359ad..63c36be20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -123,7 +123,6 @@ jobs: docker: - image: cimg/ruby:2.7 steps: - - setup_remote_docker - checkout - attach_workspace: at: . @@ -145,7 +144,6 @@ jobs: test: executor: go steps: - - setup_remote_docker - checkout - gomod - run: make test @@ -155,7 +153,6 @@ jobs: environment: CGO_ENABLED: 1 steps: - - setup_remote_docker - checkout - force-http-1 - gomod