GoogleCloudPlatform / GoogleCloudPlatform/appengine-plugins

`extraFilesDirectories` breaks re-staging: running `deploy` + `deployCron` in one build fails with `FileAlreadyExistsException`

Open
#1,076 1 comment 0 reactions 0 assignees View on GitHub
priority: p3 type: bug
Dominant language
Java
Stars
47
Forks
27
PR merge metrics
No merged PRs in 30d

Description

## Environment
- `appengine-maven-plugin` 2.8.7 (bundles `appengine-plugins-core`)
- app.yaml-based project, App Engine standard, Java 17
- One Maven invocation that both deploys the app and the cron config

## What happens
When `extraFilesDirectories` is configured and a single Maven invocation runs two goals that both stage the app — e.g. `appengine:deploy` followed by `appengine:deployCron` — the **second** staging fails:

```
Failed to execute goal com.google.cloud.tools:appengine-maven-plugin:2.8.7:deployCron ... :
com.google.cloud.tools.appengine.AppEngineException:
java.nio.file.FileAlreadyExistsException: .../target/appengine-staging/
```

Both goals stage into the same directory (`target/appengine-staging` by default). The first staging populates it; the second one aborts because the extra file already exists.

## Minimal reproduction
`pom.xml`:
```xml

com.google.cloud.tools
appengine-maven-plugin
2.8.7

my-project
1
${project.basedir}/src/main/appengine-extra

```
with any file in `src/main/appengine-extra/` (e.g. `foo.properties`), then:
```shell
mvn -B package appengine:deploy appengine:deployCron
```
`deploy` succeeds, `deployCron` fails with `FileAlreadyExistsException` on the extra file.

## Root cause
In `AppYamlProjectStaging`, staging steps are inconsistent about overwriting:

- `copyArtifact` and `copyAppEngineContext` (app.yaml) use `copyFileAndReplace` → `Files.copy(src, dest, REPLACE_EXISTING)`, so they are idempotent across re-stages.
- `copyExtraFiles` uses `copyService.copyDirectory(...)` → `FileUtil.copyDirectory(...)`, which copies **without** `REPLACE_EXISTING` and therefore throws `FileAlreadyExistsException` when a destination file already exists.

So the artifact and app.yaml survive a second staging into the same directory, but extra files do not. This still holds on `main`.

## Expected behavior
Staging should be idempotent for extra files just like it already is for the artifact and `app.yaml`. Re-staging into a populated staging directory should overwrite, not fail — otherwise `extraFilesDirectories` is unusable whenever two staging goals run in one build (a very common `deploy` + `deployCron` / `deployQueue` etc. pattern).

## Suggested fix
Make `copyExtraFiles` (i.e. `FileUtil.copyDirectory`, or the extra-files copy path specifically) overwrite existing files, consistent with `copyFileAndReplace`.

## Workaround
Give each staging goal its own staging directory so their extra-file copies never collide, e.g. a dedicated execution for `deployCron`:
```xml


cron
none
deployCron

${project.build.directory}/appengine-staging-cron

```
and invoke it as `appengine:deployCron@cron`.

Contributor guide

Open the contributing guide

Research direction

Start in AppYamlProjectStaging at copyExtraFiles and compare its copyService.copyDirectory path with copyArtifact and copyAppEngineContext, which use copyFileAndReplace. Trace FileUtil.copyDirectory and the existing staging-related tests, then verify that running both staging goals no longer fails when the destination already contains an extra file.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
build-system, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.