Skip to content

Commit 39ef782

Browse files
fix: dev mode for bundle compilation and OTP 28 tar compat
- Add skip_validation config to Compiler so dev works without the sentinel CLI binary (auto-enabled when SENTINEL_BINARY is unset) - Add local filesystem storage backend to avoid requiring MinIO/S3 in development - Fix erl_tar.create call for OTP 28 which removed in-memory binary support from create/2 - Fix drift_events migration ordering (rename to run after table creation)
1 parent 067d0a0 commit 39ef782

5 files changed

Lines changed: 119 additions & 53 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ npm-debug.log
3939
*.db
4040
*.db-shm
4141
*.db-wal
42+
43+
# Local bundle storage (dev)
44+
/priv/bundles/

config/dev.exs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,12 @@ config :phoenix_live_view,
8888
# Disable swoosh api client as it is only required for production adapters.
8989
config :swoosh, :api_client, false
9090

91-
# Bundle storage (MinIO in development)
91+
# Bundle storage (local filesystem in development, MinIO/S3 in production)
9292
config :sentinel_cp, SentinelCp.Bundles.Storage,
93-
bucket: "sentinel-bundles",
94-
ex_aws_config: [
95-
access_key_id: "minioadmin",
96-
secret_access_key: "minioadmin",
97-
scheme: "http://",
98-
host: "localhost",
99-
port: 9000,
100-
region: "us-east-1"
101-
]
93+
backend: :local,
94+
local_dir: Path.expand("../priv/bundles", __DIR__)
10295

103-
# Bundle compiler
96+
# Bundle compiler (skip validation in dev — sentinel binary not required)
10497
config :sentinel_cp, SentinelCp.Bundles.Compiler,
105-
sentinel_binary: System.get_env("SENTINEL_BINARY", "sentinel")
98+
sentinel_binary: System.get_env("SENTINEL_BINARY", "sentinel"),
99+
skip_validation: !System.get_env("SENTINEL_BINARY")

lib/sentinel_cp/bundles/compiler.ex

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,24 @@ defmodule SentinelCp.Bundles.Compiler do
1313
Returns {:ok, output} or {:error, output}.
1414
"""
1515
def validate(config_source) when is_binary(config_source) do
16-
with {:ok, tmpfile} <- write_temp_config(config_source) do
17-
try do
18-
case System.cmd(sentinel_binary(), ["validate", "--config", tmpfile],
19-
stderr_to_stdout: true
20-
) do
21-
{output, 0} -> {:ok, output}
22-
{output, _code} -> {:error, output}
16+
if skip_validation?() do
17+
Logger.info("Skipping sentinel validation (dev mode)")
18+
{:ok, "Validation skipped (dev mode)"}
19+
else
20+
with {:ok, tmpfile} <- write_temp_config(config_source) do
21+
try do
22+
case System.cmd(sentinel_binary(), ["validate", "--config", tmpfile],
23+
stderr_to_stdout: true
24+
) do
25+
{output, 0} -> {:ok, output}
26+
{output, _code} -> {:error, output}
27+
end
28+
rescue
29+
e in ErlangError ->
30+
{:error, "Failed to run sentinel binary: #{inspect(e)}"}
31+
after
32+
File.rm(tmpfile)
2333
end
24-
rescue
25-
e in ErlangError ->
26-
{:error, "Failed to run sentinel binary: #{inspect(e)}"}
27-
after
28-
File.rm(tmpfile)
2934
end
3035
end
3136
end
@@ -111,16 +116,20 @@ defmodule SentinelCp.Bundles.Compiler do
111116
end
112117

113118
defp create_tar(dir, files) do
114-
# Use Erlang's :erl_tar for portability
115-
{:ok, tar_data} =
116-
files
117-
|> Enum.map(fn file ->
119+
# Use Erlang's :erl_tar — write to temp file then read back
120+
# (OTP 28 removed in-memory binary support from create/2)
121+
tmptar = Path.join(System.tmp_dir!(), "bundle-tar-#{System.unique_integer([:positive])}.tar")
122+
123+
file_entries =
124+
Enum.map(files, fn file ->
118125
path = Path.join(dir, file)
119126
content = File.read!(path)
120127
{String.to_charlist(file), content}
121128
end)
122-
|> :erl_tar.create({:binary, []})
123129

130+
:ok = :erl_tar.create(String.to_charlist(tmptar), file_entries)
131+
tar_data = File.read!(tmptar)
132+
File.rm!(tmptar)
124133
tar_data
125134
end
126135

@@ -143,7 +152,16 @@ defmodule SentinelCp.Bundles.Compiler do
143152
end
144153

145154
defp sentinel_binary do
146-
Application.get_env(:sentinel_cp, __MODULE__, [])
155+
compiler_config()
147156
|> Keyword.get(:sentinel_binary, "sentinel")
148157
end
158+
159+
defp skip_validation? do
160+
compiler_config()
161+
|> Keyword.get(:skip_validation, false)
162+
end
163+
164+
defp compiler_config do
165+
Application.get_env(:sentinel_cp, __MODULE__, [])
166+
end
149167
end

lib/sentinel_cp/bundles/storage.ex

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,62 @@ defmodule SentinelCp.Bundles.Storage do
77
Uploads data to the configured bucket.
88
"""
99
def upload(key, data) when is_binary(data) do
10-
bucket()
11-
|> ExAws.S3.put_object(key, data, content_type: "application/gzip")
12-
|> ExAws.request(ex_aws_config())
13-
|> case do
14-
{:ok, _} -> :ok
15-
{:error, reason} -> {:error, {:upload_failed, reason}}
10+
if local_storage?() do
11+
local_upload(key, data)
12+
else
13+
bucket()
14+
|> ExAws.S3.put_object(key, data, content_type: "application/gzip")
15+
|> ExAws.request(ex_aws_config())
16+
|> case do
17+
{:ok, _} -> :ok
18+
{:error, reason} -> {:error, {:upload_failed, reason}}
19+
end
1620
end
1721
end
1822

1923
@doc """
2024
Downloads data from the configured bucket.
2125
"""
2226
def download(key) do
23-
bucket()
24-
|> ExAws.S3.get_object(key)
25-
|> ExAws.request(ex_aws_config())
26-
|> case do
27-
{:ok, %{body: body}} -> {:ok, body}
28-
{:error, reason} -> {:error, {:download_failed, reason}}
27+
if local_storage?() do
28+
local_download(key)
29+
else
30+
bucket()
31+
|> ExAws.S3.get_object(key)
32+
|> ExAws.request(ex_aws_config())
33+
|> case do
34+
{:ok, %{body: body}} -> {:ok, body}
35+
{:error, reason} -> {:error, {:download_failed, reason}}
36+
end
2937
end
3038
end
3139

3240
@doc """
3341
Generates a presigned URL for downloading a bundle.
3442
"""
3543
def presigned_url(key, expires_in \\ 3600) do
36-
config = ex_aws_config()
37-
38-
ExAws.S3.presigned_url(config, :get, bucket(), key, expires_in: expires_in)
44+
if local_storage?() do
45+
{:ok, "/local-storage/#{key}"}
46+
else
47+
config = ex_aws_config()
48+
ExAws.S3.presigned_url(config, :get, bucket(), key, expires_in: expires_in)
49+
end
3950
end
4051

4152
@doc """
4253
Deletes an object from the configured bucket.
4354
"""
4455
def delete(key) do
45-
bucket()
46-
|> ExAws.S3.delete_object(key)
47-
|> ExAws.request(ex_aws_config())
48-
|> case do
49-
{:ok, _} -> :ok
50-
{:error, reason} -> {:error, {:delete_failed, reason}}
56+
if local_storage?() do
57+
local_delete(key)
58+
else
59+
bucket()
60+
|> ExAws.S3.delete_object(key)
61+
|> ExAws.request(ex_aws_config())
62+
|> case do
63+
{:ok, _} -> :ok
64+
{:error, reason} -> {:error, {:delete_failed, reason}}
65+
end
5166
end
5267
end
5368

@@ -59,12 +74,48 @@ defmodule SentinelCp.Bundles.Storage do
5974
end
6075

6176
defp bucket do
62-
Application.get_env(:sentinel_cp, __MODULE__, [])
77+
storage_config()
6378
|> Keyword.get(:bucket, "sentinel-bundles")
6479
end
6580

6681
defp ex_aws_config do
67-
Application.get_env(:sentinel_cp, __MODULE__, [])
82+
storage_config()
6883
|> Keyword.get(:ex_aws_config, [])
6984
end
85+
86+
defp local_storage? do
87+
storage_config()
88+
|> Keyword.get(:backend, :s3) == :local
89+
end
90+
91+
defp local_storage_dir do
92+
storage_config()
93+
|> Keyword.get(:local_dir, Path.expand("priv/bundles"))
94+
end
95+
96+
defp storage_config do
97+
Application.get_env(:sentinel_cp, __MODULE__, [])
98+
end
99+
100+
defp local_upload(key, data) do
101+
path = Path.join(local_storage_dir(), key)
102+
File.mkdir_p!(Path.dirname(path))
103+
File.write!(path, data)
104+
:ok
105+
end
106+
107+
defp local_download(key) do
108+
path = Path.join(local_storage_dir(), key)
109+
110+
case File.read(path) do
111+
{:ok, data} -> {:ok, data}
112+
{:error, reason} -> {:error, {:download_failed, reason}}
113+
end
114+
end
115+
116+
defp local_delete(key) do
117+
path = Path.join(local_storage_dir(), key)
118+
File.rm(path)
119+
:ok
120+
end
70121
end

priv/repo/migrations/20260205171852_add_drift_severity.exs renamed to priv/repo/migrations/20260205172334_add_drift_severity.exs

File renamed without changes.

0 commit comments

Comments
 (0)