Skip to content

Commit

Permalink
Deployers phoenix improvements (#4113)
Browse files Browse the repository at this point in the history
* Fix phoenix release env

* Support custom tool-versions for phoenix

* Always gen files at launch

* Test phoenix

* Fix lint

refer to https://github.com/superfly/flyctl/actions/runs/12236467772/job/34129993067?pr=4113

* Fix phoenix test

* this is actually useless

* Add TestDeployPhoenixSqlite

* Fix phoenix for real

* well thats the fix
  • Loading branch information
lubien authored Dec 17, 2024
1 parent a6fc203 commit c76b965
Show file tree
Hide file tree
Showing 120 changed files with 5,189 additions and 16 deletions.
5 changes: 3 additions & 2 deletions deploy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
CREATE_AND_PUSH_BRANCH = !get_env("DEPLOY_CREATE_AND_PUSH_BRANCH").nil?
FLYIO_BRANCH_NAME = "flyio-new-files"

DEPLOY_TRIGGER = get_env("DEPLOY_TRIGGER")
DEPLOYER_FLY_CONFIG_PATH = get_env("DEPLOYER_FLY_CONFIG_PATH")
DEPLOYER_SOURCE_CWD = get_env("DEPLOYER_SOURCE_CWD")
DEPLOY_APP_NAME = get_env("DEPLOY_APP_NAME")
Expand Down Expand Up @@ -268,7 +269,7 @@
ORG_SLUG = manifest["plan"]["org"]
APP_REGION = manifest["plan"]["region"]

DO_GEN_REQS = !DEPLOY_COPY_CONFIG || !HAS_FLY_CONFIG
DO_GEN_REQS = DEPLOY_TRIGGER == "launch"

debug("generate reqs? #{DO_GEN_REQS}")

Expand Down Expand Up @@ -314,7 +315,7 @@
APP_NAME = DEPLOY_APP_NAME || fly_config["app"]

image_ref = in_step Step::BUILD do
image_tag = SecureRandom.hex(16)
image_tag = "deployment-#{SecureRandom.hex(16)}"
if (image_ref = fly_config.dig("build","image")&.strip) && !image_ref.nil? && !image_ref.empty?
info("Skipping build, using image defined in fly config: #{image_ref}")
image_ref
Expand Down
2 changes: 1 addition & 1 deletion deployer.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ RUN git clone https://github.com/asdf-vm/asdf.git $HOME/.asdf --branch v0.14.0 &
asdf install nodejs $DEFAULT_NODE_VERSION && asdf global nodejs $DEFAULT_NODE_VERSION && \
# elixir
asdf plugin-add erlang https://github.com/michallepicki/asdf-erlang-prebuilt-ubuntu-20.04.git && \
echo -e "local.hex\nlocal.rebar" > $HOME/.default-mix-commands && \
asdf plugin add elixir https://github.com/asdf-vm/asdf-elixir.git && \
asdf install erlang $DEFAULT_ERLANG_VERSION && asdf global erlang $DEFAULT_ERLANG_VERSION && \
asdf install elixir $DEFAULT_ELIXIR_VERSION && asdf global elixir $DEFAULT_ELIXIR_VERSION && \
mix local.hex --force && mix local.rebar --force && \
# bun
asdf plugin add bun https://github.com/cometkim/asdf-bun.git && \
asdf install bun $DEFAULT_BUN_VERSION && asdf global bun $DEFAULT_BUN_VERSION
Expand Down
9 changes: 0 additions & 9 deletions internal/command/launch/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,3 @@ func (state *launchState) createApp(ctx context.Context) (*fly.App, error) {

return app, nil
}

func (state *launchState) getApp(ctx context.Context) (*fly.App, error) {
apiClient := flyutil.ClientFromContext(ctx)
app, err := apiClient.GetApp(ctx, state.Plan.AppName)
if err != nil {
return nil, err
}
return app, nil
}
28 changes: 28 additions & 0 deletions scanner/phoenix.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ func configurePhoenix(sourceDir string, config *ScannerConfig) (*SourceInfo, err
},
}

// This adds support on launch UI for repos with different .tool-versions
deployTrigger := os.Getenv("DEPLOY_TRIGGER")
if deployTrigger == "launch" && helpers.FileExists(filepath.Join(sourceDir, ".tool-versions")) {
cmd := exec.Command("asdf", "install")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return nil, errors.Wrap(err, "We identified .tool-versions but after running `asdf install` we ran into some errors. Please check that your `asdf install` builds successfully and try again.")
}

cmd = exec.Command("mix", "local.hex", "--force")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return nil, errors.Wrap(err, "After installing your elixir version with asdf we found an error while running `mix local.hex --force`. Please confirm that running this command works locally and try again.")
}

cmd = exec.Command("mix", "local.rebar", "--force")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return nil, errors.Wrap(err, "After installing your elixir version with asdf we found an error while running `mix local.rebar --force`. Please confirm that running this command works locally and try again.")
}
}

// We found Phoenix, so check if the project compiles.
cmd := exec.Command("mix", "do", "deps.get,", "compile")
cmd.Stdout = os.Stdout
Expand Down
4 changes: 0 additions & 4 deletions scanner/rails.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,10 +444,6 @@ The following comand can be used to update your Dockerfile:
}
}

if srcInfo.DatabaseDesired == DatabaseKindSqlite {

}

// add HealthCheck (if found)
srcInfo.HttpCheckPath = <-healthcheck_channel
if srcInfo.HttpCheckPath != "" {
Expand Down
48 changes: 48 additions & 0 deletions test/deployer/deployer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,54 @@ func TestLaunchStatic(t *testing.T) {
require.Contains(t, string(body), "<body>Hello World</body>")
}

func TestDeployPhoenixSqlite(t *testing.T) {
deploy := testDeployer(t,
withFixtureApp("deploy-phoenix-sqlite"),
createRandomApp,
withOverwrittenConfig(func(d *testlib.DeployTestRun) map[string]any {
return map[string]any{
"app": d.Extra["appName"],
"region": d.PrimaryRegion(),
"env": map[string]string{
"TEST_ID": d.ID(),
},
}
}),
testlib.DeployOnly,
testlib.DeployNow,
withWorkDirAppSource,
)

body, err := testlib.RunHealthCheck(fmt.Sprintf("https://%s.fly.dev", deploy.Extra["appName"].(string)))
require.NoError(t, err)

require.Contains(t, string(body), "Phoenix")
}

func TestDeployPhoenixSqliteWithCustomToolVersions(t *testing.T) {
deploy := testDeployer(t,
withFixtureApp("deploy-phoenix-sqlite-custom-tool-versions"),
createRandomApp,
withOverwrittenConfig(func(d *testlib.DeployTestRun) map[string]any {
return map[string]any{
"app": d.Extra["appName"],
"region": d.PrimaryRegion(),
"env": map[string]string{
"TEST_ID": d.ID(),
},
}
}),
testlib.DeployOnly,
testlib.DeployNow,
withWorkDirAppSource,
)

body, err := testlib.RunHealthCheck(fmt.Sprintf("https://%s.fly.dev", deploy.Extra["appName"].(string)))
require.NoError(t, err)

require.Contains(t, string(body), "Phoenix")
}

func createRandomApp(d *testlib.DeployTestRun) {
appName := d.CreateRandomAppName()
require.NotEmpty(d, appName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This file excludes paths from the Docker build context.
#
# By default, Docker's build context includes all files (and folders) in the
# current directory. Even if a file isn't copied into the container it is still sent to
# the Docker daemon.
#
# There are multiple reasons to exclude files from the build context:
#
# 1. Prevent nested folders from being copied into the container (ex: exclude
# /assets/node_modules when copying /assets)
# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc)
# 3. Avoid sending files containing sensitive information
#
# More information on using .dockerignore is available here:
# https://docs.docker.com/engine/reference/builder/#dockerignore-file

.dockerignore

# Ignore git, but keep git HEAD and refs to access current commit hash if needed:
#
# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat
# d0b8727759e1e0e7aa3d41707d12376e373d5ecc
.git
!.git/HEAD
!.git/refs

# Common development/test artifacts
/cover/
/doc/
/test/
/tmp/
.elixir_ls

# Mix artifacts
/_build/
/deps/
*.ez

# Generated on crash by the VM
erl_crash.dump

# Static artifacts - These should be fetched and built inside the Docker image
/assets/node_modules/
/priv/static/assets/
/priv/static/cache_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
import_deps: [:ecto, :phoenix],
inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
subdirectories: ["priv/*/migrations"]
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# The directory Mix will write compiled artifacts to.
/_build/
/.elixir_ls/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
hello_elixir-*.tar

# Ignore assets that are produced by build tools.
/priv/static/assets/

# Ignore digested assets cache.
/priv/static/cache_manifest.json

# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/

hello_elixir_dev.db*
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
erlang 24.3.4.9
elixir 1.12.3-otp-24
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian instead of
# Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
# https://hub.docker.com/_/ubuntu?tab=tags
#
#
# This file is based on these images:
#
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20210902-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.13.3-erlang-24.0.5-debian-bullseye-20210902-slim
#
ARG ELIXIR_VERSION=1.13.3
ARG OTP_VERSION=24.0.5
ARG DEBIAN_VERSION=bullseye-20210902-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM ${BUILDER_IMAGE} as builder

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

# prepare build dir
WORKDIR /app

# install hex + rebar
RUN mix local.hex --force && \
mix local.rebar --force

# set build ENV
ENV MIX_ENV="prod"

# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config

# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY priv priv

COPY lib lib

COPY assets assets

# compile assets
RUN mix assets.deploy

# Compile the release
RUN mix compile

# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/

COPY rel rel
RUN mix release

# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales sqlite3 \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

WORKDIR "/app"
RUN chown nobody /app

# set runner ENV
ENV MIX_ENV="prod"

# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/hello_elixir ./

USER nobody

CMD ["/app/bin/server"]
# Appended by flyctl
ENV ECTO_IPV6 true
ENV ERL_AFLAGS "-proto_dist inet6_tcp"
Loading

0 comments on commit c76b965

Please sign in to comment.