Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configure temporary directory for local execute #967

Merged
merged 14 commits into from
Dec 5, 2023
Merged
52 changes: 26 additions & 26 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -312,38 +312,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:
Expand Down
1 change: 1 addition & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":"<version-of-build-agent>"}'`)
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
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,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")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ token: asdf
)
})

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()
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
67 changes: 41 additions & 26 deletions local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(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
Expand All @@ -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(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)
Expand Down Expand Up @@ -111,13 +123,13 @@ func AddFlagsForDocumentation(flags *pflag.FlagSet) {
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
Expand All @@ -135,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)...)
}
})
Expand Down Expand Up @@ -199,14 +217,11 @@ func ensureDockerIsAvailable() (string, error) {
}

// Write data to a temp file, and return the path to that file.
func writeStringToTempFile(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")
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")
Expand All @@ -219,13 +234,13 @@ 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"
core := []string{"docker", "run", "--interactive", "--tty", "--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()),
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",
"--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...)
Expand Down
16 changes: 7 additions & 9 deletions local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,24 @@ 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("/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", "/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", "/tmp/local_build_config.yml",
"--config", "/tempdir/local_build_config.yml",
"--job", "build",
"extra-1", "extra-2",
))
})

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"))
Expand Down
1 change: 1 addition & 0 deletions settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
TempDir string `yaml:"temp_dir,omitempty"`
}

type OrbPublishingInfo struct {
Expand Down