Skip to content

Commit de5ca3b

Browse files
authored
feat: add support for separate attach command (#63)
This closes #60 This closes #59 BREAKING CHANGE: neovim configuration is no longer automatically mounted It has to be enabled in setup It is also only mounted if attaching directly To better support separate attach command, set attach_mounts.always flag to true in setup, to always mount configured neovim points
1 parent fda41b4 commit de5ca3b

File tree

7 files changed

+261
-69
lines changed

7 files changed

+261
-69
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,22 +91,26 @@ require("devcontainer").setup {
9191
-- This can be useful to mount local configuration
9292
-- And any other mounts when attaching to containers with this plugin
9393
attach_mounts = {
94+
-- Can be set to true to always mount items defined below
95+
-- And not only when directly attaching
96+
-- This can be useful if executing attach command separately
97+
always = false,
9498
neovim_config = {
9599
-- enables mounting local config to /root/.config/nvim in container
96-
enabled = true,
100+
enabled = false,
97101
-- makes mount readonly in container
98102
options = { "readonly" }
99103
},
100104
neovim_data = {
101105
-- enables mounting local data to /root/.local/share/nvim in container
102-
enabled = true,
106+
enabled = false,
103107
-- no options by default
104108
options = {}
105109
},
106110
-- Only useful if using neovim 0.8.0+
107111
neovim_state = {
108112
-- enables mounting local state to /root/.local/state/nvim in container
109-
enabled = true,
113+
enabled = false,
110114
-- no options by default
111115
options = {}
112116
},
@@ -136,6 +140,8 @@ If not disabled by using `generate_commands = false` in setup, this plugin provi
136140
- `DevcontainerComposeDown` - run docker-compose down based on devcontainer.json
137141
- `DevcontainerComposeRm` - run docker-compose rm based on devcontainer.json
138142
- `DevcontainerStartAuto` - start whatever is defined in devcontainer.json
143+
- `DevcontainerStartAutoAndAttach` - start and attach to whatever is defined in devcontainer.json
144+
- `DevcontainerAttachAuto` - attach to whatever is defined in devcontainer.json
139145
- `DevcontainerStopAuto` - stop whatever was started based on devcontainer.json
140146
- `DevcontainerStopAll` - stop everything started with this plugin (in current session)
141147
- `DevcontainerRemoveAll` - remove everything started with this plugin (in current session)

lua/devcontainer/commands.lua

Lines changed: 118 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ end
5454

5555
local function generate_common_run_command_args(data)
5656
local run_args = nil
57-
-- TODO: Add support for remoteEnv?
5857
if data.forwardPorts then
5958
run_args = run_args or {}
6059
for _, v in ipairs(data.forwardPorts) do
@@ -67,19 +66,20 @@ end
6766

6867
local function generate_run_command_args(data, attaching)
6968
local run_args = generate_common_run_command_args(data)
69+
-- TODO: Add support for containerEnv!
7070
if data.containerUser then
7171
run_args = run_args or {}
7272
table.insert(run_args, "--user")
7373
table.insert(run_args, data.containerUser)
7474
end
7575
if data.workspaceFolder or data.workspaceMount then
76-
if data.workspaceMount == nil or data.workspaceFolder == nil then
77-
vim.notify("workspaceFolder and workspaceMount have to both be defined to be used!", vim.log.levels.WARN)
78-
else
79-
run_args = run_args or {}
80-
table.insert(run_args, "--mount")
81-
table.insert(run_args, data.workspaceMount)
82-
end
76+
-- if data.workspaceMount == nil or data.workspaceFolder == nil then
77+
-- vim.notify("workspaceFolder and workspaceMount have to both be defined to be used!", vim.log.levels.WARN)
78+
-- else
79+
run_args = run_args or {}
80+
table.insert(run_args, "--mount")
81+
table.insert(run_args, data.workspaceMount)
82+
-- end
8383
end
8484
if data.mounts then
8585
run_args = run_args or {}
@@ -95,7 +95,7 @@ local function generate_run_command_args(data, attaching)
9595
table.insert(run_args, v)
9696
end
9797
end
98-
if attaching and plugin_config.attach_mounts then
98+
if plugin_config.attach_mounts and (attaching or plugin_config.attach_mounts.always) then
9999
run_args = run_args or {}
100100
local am = plugin_config.attach_mounts
101101

@@ -152,6 +152,21 @@ local function generate_run_command_args(data, attaching)
152152
return run_args
153153
end
154154

155+
local function generate_exec_command_args(data)
156+
local exec_args = nil
157+
-- remoteEnv currently unsupported
158+
if data.workspaceFolder or data.workspaceMount then
159+
-- if data.workspaceMount == nil or data.workspaceFolder == nil then
160+
-- vim.notify("workspaceFolder and workspaceMount have to both be defined to be used!", vim.log.levels.WARN)
161+
-- else
162+
exec_args = exec_args or {}
163+
table.insert(exec_args, "--workdir")
164+
table.insert(exec_args, data.workspaceFolder)
165+
-- end
166+
end
167+
return exec_args
168+
end
169+
155170
local function generate_compose_up_command_args(data)
156171
local run_args = nil
157172
if data.runServices then
@@ -339,23 +354,55 @@ function M.docker_image_run(callback)
339354
end)
340355
end
341356

342-
local function spawn_docker_build_and_run(data, on_success, add_neovim)
357+
local function attach_to_container(data, container_id, on_success)
358+
docker.exec(container_id, {
359+
tty = true,
360+
command = "nvim",
361+
args = generate_exec_command_args(data),
362+
on_success = on_success,
363+
on_fail = function()
364+
vim.notify("Attaching to container (" .. container_id .. ") failed!", vim.log.levels.ERROR)
365+
end,
366+
})
367+
end
368+
369+
local function attach_to_compose_service(data, on_success)
370+
if not data.service then
371+
vim.notify(
372+
"service must be defined in " .. data.metadata.file_path .. " to attach to docker compose",
373+
vim.log.levels.ERROR
374+
)
375+
return
376+
end
377+
vim.notify("Found docker compose file definition. Attaching to service: " .. data.service)
378+
docker_compose.get_container_id(data.dockerComposeFile, data.service, {
379+
on_success = function(container_id)
380+
attach_to_container(data, container_id, function()
381+
on_success(data)
382+
end)
383+
end,
384+
})
385+
end
386+
387+
local function spawn_docker_build_and_run(data, on_success, add_neovim, attach)
343388
docker.build(data.build.dockerfile, data.build.context, {
344389
args = generate_build_command_args(data),
345390
add_neovim = add_neovim,
346391
on_success = function(image_id)
347392
docker.run(image_id, {
348393
args = generate_run_command_args(data, add_neovim),
349-
tty = add_neovim,
350-
-- TODO: Potentially add in the future for better compatibility
351-
-- or (data.overrideCommand and {
352-
-- "/bin/sh",
353-
-- "-c",
354-
-- "'while sleep 1000; do :; done'",
355-
-- })
356-
command = (add_neovim and "nvim") or nil,
394+
-- -- TODO: Potentially add in the future for better compatibility
395+
-- -- or (data.overrideCommand and {
396+
-- -- "/bin/sh",
397+
-- -- "-c",
398+
-- -- "'while sleep 1000; do :; done'",
399+
-- -- })
357400
on_success = function(container_id)
358-
on_success(data, image_id, container_id)
401+
if attach then
402+
attach_to_container(data, container_id, function()
403+
on_success(data, image_id, container_id)
404+
end)
405+
end
359406
end,
360407
on_fail = function()
361408
vim.notify("Running built image (" .. image_id .. ") failed!", vim.log.levels.ERROR)
@@ -389,7 +436,7 @@ local function execute_docker_build_and_run(callback, add_neovim)
389436
)
390437
return
391438
end
392-
spawn_docker_build_and_run(data, on_success, add_neovim)
439+
spawn_docker_build_and_run(data, on_success, add_neovim, add_neovim)
393440
end)
394441
end
395442

@@ -421,8 +468,9 @@ end
421468
---Then it looks for dockerfile
422469
---And last it looks for image
423470
---@param callback function|nil called on success - devcontainer config is passed to the callback
471+
---@param attach boolean|nil if true, automatically attach after starting
424472
---@usage `require("devcontainer.commands").start_auto()`
425-
function M.start_auto(callback)
473+
function M.start_auto(callback, attach)
426474
vim.validate({
427475
callback = { callback, { "function", "nil" } },
428476
})
@@ -438,7 +486,11 @@ function M.start_auto(callback)
438486
docker_compose.up(data.dockerComposeFile, {
439487
args = generate_compose_up_command_args(data),
440488
on_success = function()
441-
on_success(data)
489+
if attach then
490+
attach_to_compose_service(data, on_success)
491+
else
492+
on_success(data)
493+
end
442494
end,
443495
on_fail = function()
444496
vim.notify("Docker compose up failed!", vim.log.levels.ERROR)
@@ -449,14 +501,14 @@ function M.start_auto(callback)
449501

450502
if data.build.dockerfile then
451503
vim.notify("Found dockerfile definition. Running docker build and run...")
452-
spawn_docker_build_and_run(data, on_success, false)
504+
spawn_docker_build_and_run(data, on_success, attach, attach)
453505
return
454506
end
455507

456508
if data.image then
457509
vim.notify("Found image definition. Running docker run...")
458510
docker.run(data.image, {
459-
args = generate_run_command_args(data, false),
511+
args = generate_run_command_args(data, attach),
460512
on_success = function(_)
461513
on_success(data)
462514
end,
@@ -469,6 +521,48 @@ function M.start_auto(callback)
469521
end)
470522
end
471523

524+
---Parses devcontainer.json and attaches to whatever is defined there
525+
---Looks for dockerComposeFile first
526+
---Then it looks for dockerfile
527+
---And last it looks for image
528+
---@param callback function|nil called on success - devcontainer config is passed to the callback
529+
---@usage `require("devcontainer.commands").attach_auto()`
530+
function M.attach_auto(callback)
531+
vim.validate({
532+
callback = { callback, { "function", "nil" } },
533+
})
534+
535+
local on_success = callback
536+
or function(config)
537+
vim.notify("Successfully attached to container from " .. config.metadata.file_path)
538+
end
539+
540+
get_nearest_devcontainer_config(function(data)
541+
if data.dockerComposeFile then
542+
attach_to_compose_service(data, on_success)
543+
return
544+
end
545+
546+
if data.build.dockerfile then
547+
vim.notify("Found dockerfile definition. Attaching to the container...")
548+
local container = status.find_container({ source_dockerfile = data.build.dockerfile })
549+
attach_to_container(data, container.container_id, function()
550+
on_success(data)
551+
end)
552+
return
553+
end
554+
555+
if data.image then
556+
vim.notify("Found image definition. Attaaching to the container...")
557+
local container = status.find_container({ source_dockerfile = data.build.dockerfile })
558+
attach_to_container(data, container.container_id, function()
559+
on_success(data)
560+
end)
561+
return
562+
end
563+
end)
564+
end
565+
472566
---Parses devcontainer.json and stops whatever is defined there
473567
---Looks for dockerComposeFile first
474568
---Then it looks for dockerfile

lua/devcontainer/config.lua

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,26 +149,30 @@ M.devcontainer_json_template = default_devcontainer_json_template
149149
---@field options List[string]|nil additional bind options, useful to define { "readonly" }
150150

151151
---@class AttachMountsOpts
152+
---@field always boolean|nil if true these mounts are used on every run, to be available when attaching later
152153
---@field neovim_config MountOpts|nil if true attaches neovim local config to /root/.config/nvim in container
153154
---@field neovim_data MountOpts|nil if true attaches neovim data to /root/.local/share/nvim in container
154155
---@field neovim_state MountOpts|nil if true attaches neovim state to /root/.local/state/nvim in container
155156
---@field custom_mounts List[string] list of custom mounts to add when attaching
156157

157158
---Configuration for mounts when using attach command
159+
---NOTE: when attaching in a separate command, it is useful to set
160+
---always to true, since these have to be attached when starting
158161
---Useful to mount neovim configuration into container
159162
---Applicable only to `devcontainer.commands` functions!
160163
---@type AttachMountsOpts
161164
M.attach_mounts = {
165+
always = false,
162166
neovim_config = {
163-
enabled = true,
167+
enabled = false,
164168
options = { "readonly" },
165169
},
166170
neovim_data = {
167-
enabled = true,
171+
enabled = false,
168172
options = {},
169173
},
170174
neovim_state = {
171-
enabled = true,
175+
enabled = false,
172176
options = {},
173177
},
174178
custom_mounts = {},

lua/devcontainer/docker-compose.lua

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,33 +117,45 @@ function M.down(compose_file, opts)
117117
end)
118118
end
119119

120-
---@class DockerComposeRmOpts
121-
---@field on_success function() success callback
120+
---@class DockerComposeGetContainerIdOpts
121+
---@field on_success function(container_id) success callback
122122
---@field on_fail function() failure callback
123123

124-
---Run docker-compose rm with passed file
124+
---Run docker-compose ps with passed file and service to get its container_id
125125
---@param compose_file string|table path to docker-compose.yml file or files
126-
---@param opts DockerComposeRmOpts Additional options including callbacks
127-
---@usage `require("devcontainer.docker-compose").rm("docker-compose.yml")`
128-
function M.rm(compose_file, opts)
126+
---@param service string service name
127+
---@param opts DockerComposeGetContainerIdOpts Additional options including callbacks
128+
---@usage `docker_compose.get_container_id("docker-compose.yml", { on_success = function(container_id) end })`
129+
function M.get_container_id(compose_file, service, opts)
129130
vim.validate({
130131
compose_file = { compose_file, { "string", "table" } },
132+
service = { service, "string" },
131133
})
132134
opts = opts or {}
133135
v.validate_callbacks(opts)
134136
local on_success = opts.on_success
135-
or function()
136-
vim.notify("Successfully removed containers from " .. compose_file)
137+
or function(container_id)
138+
vim.notify("Container id of service " .. service .. " from " .. compose_file .. " is " .. container_id)
137139
end
138140
local on_fail = opts.on_fail
139141
or function()
140-
vim.notify("Removing containers from " .. compose_file .. " failed!", vim.log.levels.ERROR)
142+
vim.notify(
143+
"Fetching container id for " .. service .. " from " .. compose_file .. " failed!",
144+
vim.log.levels.ERROR
145+
)
141146
end
142147
local command = get_compose_files_command(compose_file)
143-
vim.list_extend(command, { "rm", "-fsv" })
144-
run_docker_compose(command, nil, function(code, _)
148+
vim.list_extend(command, { "ps", "-q", service })
149+
local container_id = nil
150+
run_docker_compose(command, {
151+
stdout = function(_, data)
152+
if data then
153+
container_id = vim.split(data, "\n")[1]
154+
end
155+
end,
156+
}, function(code, _)
145157
if code == 0 then
146-
on_success()
158+
on_success(container_id)
147159
else
148160
on_fail()
149161
end

0 commit comments

Comments
 (0)