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

No possibility to delete named volumes #369

Open
piotrminkina opened this issue Aug 26, 2022 · 2 comments
Open

No possibility to delete named volumes #369

piotrminkina opened this issue Aug 26, 2022 · 2 comments

Comments

@piotrminkina
Copy link

Hello,

First of all, thank you for the plugin. It gets the job done!

Currently there is no way to delete named volumes. This is implemented by the docker-compose down --volumes command, but there is no equivalent in your plugin.

Regards
Piotr Minkina

@augi
Copy link
Member

augi commented Aug 29, 2022

Hello,

thank you! There is actually removeVolumes option that should add --volumes to the down command: https://github.com/avast/gradle-docker-compose-plugin/blob/main/src/main/groovy/com/avast/gradle/dockercompose/tasks/ComposeDownForced.groovy#L78

Is this what you are actually looking for? 🙏

@augi augi added the question label Aug 29, 2022
@piotrminkina
Copy link
Author

Hello,

I did not notice this in the implementation, but unfortunately it does not solve my problem, i.e. named volumes are not deleted. In my case, the Plugin executes most likely the block

if (!startedServices.get().empty) {
args += ['rm', '-f']
if (removeVolumes.get()) {
args += ['-v']
}
args += servicesToStop
. The -v parameter used there is only responsible for deleting anonymous volumes. My configuration of this Plugin is as follows.

task appUp() { /** ... */ }
task appDown() { /** ... */ }
task logsUp() { /** ... */ }
task logsDown() { /** ... */ }
task docsUp() { /** ... */ }
task docsDown() { /** ... */ }

clean {
    dependsOn appDown, logsDown, docsDown
}

dockerCompose {
    isRequiredBy(tasks.appUp)
    isRequiredBy(tasks.test)
    projectName = env.COMPOSE_PROJECT_NAME.value
    useComposeFiles = env.COMPOSE_FILE.value.tokenize(env.COMPOSE_PATH_SEPARATOR.value)
    // Profiles aren't supported by this plugin
    composeAdditionalArgs = ['--profile=app']
    // It should be detected automatically on the basis of the profile, but is not supported
    startedServices = ['nginx', 'artifactory', 'mountebank', 'squid']
    waitForHealthyStateTimeout = Duration.ofMinutes(3)
    includeDependencies = true
    stopContainers = false

    docs {
        isRequiredBy(tasks.docsUp)
        projectName = env.COMPOSE_PROJECT_NAME.value
        // Profiles aren't supported by this plugin
        composeAdditionalArgs = ['--profile=docs']
        // It should be detected automatically on the basis of the profile, but is not supported
        startedServices = ['mkdocs', 'kroki']
    }
    logs {
        isRequiredBy(tasks.logsUp)
        // Currently no implementation for logs testing
        //isRequiredBy(tasks.test)
        projectName = env.COMPOSE_PROJECT_NAME.value
        // Profiles aren't supported by this plugin
        composeAdditionalArgs = ['--profile=logs']
        // It should be detected automatically on the basis of the profile, but is not supported
        startedServices = ['filebeat']
    }
}

Executing one of the three configured stacks looks like the following:

$ ./gradlew appUp
> Task :composeBuild
[...]
> Task :composeUp
Creating artifactory_squid ... 
Creating artifactory_mountebank ... 
Creating artifactory_mountebank ... done
Creating artifactory_squid      ... done
Creating artifactory_artifactory ... 
Creating artifactory_artifactory ... done
Creating artifactory_nginx       ... 
Creating artifactory_nginx       ... done
[...]
> Task :composeDown
You're trying to stop the containers, but stopContainers is set to false. Please use composeDownForced task instead.
[...]

This will also automatically create named volumes.

$ docker volume ls --filter=label=com.docker.compose.project=artifactory
DRIVER    VOLUME NAME
local     artifactory_artifactory_var
local     artifactory_nginx_cache_proxy

When I want to delete the whole stack, I do it with the command as below, but unfortunately the named volumes are not deleted.

$ ./gradlew clean
> Task :composeDownForced
Stopping artifactory_nginx       ... 
Stopping artifactory_artifactory ... 
Stopping artifactory_mountebank  ... 
Stopping artifactory_squid       ... 
Stopping artifactory_nginx       ... done
Stopping artifactory_artifactory ... done
Stopping artifactory_mountebank  ... done
Stopping artifactory_squid       ... done
Removing artifactory_nginx       ... 
Removing artifactory_artifactory ... 
Removing artifactory_mountebank  ... 
Removing artifactory_squid       ... 
Removing artifactory_squid       ... done
Removing artifactory_nginx       ... done
Removing artifactory_mountebank  ... done
Removing artifactory_artifactory ... done
Going to remove artifactory_nginx, artifactory_artifactory, artifactory_mountebank, artifactory_squid

> Task :docsComposeDownForced
No stopped containers

> Task :logsComposeDownForced
No stopped containers
[...]

The way I currently deal with this problem is to define an additional task volumesDown, which takes care of deleting the named volumes, at the appropriate time.

task volumesDown() {
    dependsOn appDown, logsDown, docsDown
    /** ... */

    doLast {
        def projectName = dockerCompose.projectName.get()
        def executor = dockerCompose.dockerExecutor

        executor
            .execute(
                'volume',
                'ls',
                '--filter=label=com.docker.compose.project=' + projectName,
                '--format={{ .Name }}'
            )
            .eachLine({
                executor.execute('volume', 'rm', it)
            })
    }
}

clean {
    dependsOn appDown, logsDown, docsDown, volumesDown
}

To cover my case in your Plugin, I think somewhere in the block

there should be an implementation that additionally removes named volumes from the project that are no longer used by other containers.

Example code implementing this case:

def projectName = dockerCompose.projectName.get()
def executor = dockerCompose.dockerExecutor

executor
    .execute(
        'volume',
        'ls',
        '--filter=label=com.docker.compose.project=' + projectName,
        '--format={{ .Name }}'
    )
    .eachLine({ volumeName ->
        def containersNames = executor
            .execute(
                'ps',
                '--all',
                "--filter=volume=${volumeName}",
                '--format={{ .Names }}'
            )
            .tokenize('\n')

        if (containersNames) {
            logger.lifecycle('Volume {} is used by: {}', volumeName, containersNames.join(', '))
        } else {
            executor.execute(
                'volume',
                'rm',
                volumeName
            )
            logger.lifecycle('Removed volume {}', volumeName)
        }
    })

The above code can produce this output:

Removed volume artifactory_artifactory_var
Volume artifactory_nginx_cache_proxy is used by: another_container, artifactory_nginx

Greetings
Piotr Minkina

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants