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

Support non-EC2 targets with ob deploy #658

Merged
merged 9 commits into from
Mar 12, 2020
17 changes: 11 additions & 6 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ let
obelisk-cliapp = self.callCabal2nix "obelisk-cliapp" (cleanSource ./lib/cliapp) {};
obelisk-command = haskellLib.overrideCabal (self.callCabal2nix "obelisk-command" (cleanSource ./lib/command) {}) {
librarySystemDepends = [
pkgs.jre
pkgs.nix
(haskellLib.justStaticExecutables self.ghcid)
];
Expand Down Expand Up @@ -173,18 +174,21 @@ in rec {
'';

serverModules = {
mkBaseEc2 = { hostName, routeHost, enableHttps, adminEmail, ... }: {...}: {
mkBaseEc2 = { nixosPkgs, ... }: {...}: {
imports = [
(pkgs.path + /nixos/modules/virtualisation/amazon-image.nix)
(nixosPkgs.path + /nixos/modules/virtualisation/amazon-image.nix)
];
ec2.hvm = true;
};

mkDefaultNetworking = { adminEmail, enableHttps, hostName, routeHost, ... }: {...}: {
networking = {
inherit hostName;
firewall.allowedTCPPorts = if enableHttps then [ 80 443 ] else [ 80 ];
};
security.acme.certs = if enableHttps then {
"${routeHost}".email = adminEmail;
} else {};
ec2.hvm = true;
};

mkObeliskApp =
Expand Down Expand Up @@ -251,14 +255,15 @@ in rec {
echo ${version} > $out/version
'';

server = { exe, hostName, adminEmail, routeHost, enableHttps, version }@args:
server = { exe, hostName, adminEmail, routeHost, enableHttps, version, module ? serverModules.mkBaseEc2 }@args:
let
nixos = import (pkgs.path + /nixos);
in nixos {
system = "x86_64-linux";
configuration = {
imports = [
(serverModules.mkBaseEc2 args)
(module { inherit exe hostName adminEmail routeHost enableHttps version; nixosPkgs = pkgs; })
(serverModules.mkDefaultNetworking args)
(serverModules.mkObeliskApp args)
];
};
Expand Down Expand Up @@ -385,7 +390,7 @@ in rec {
linuxExeConfigurable = linuxExe;
linuxExe = linuxExe dummyVersion;
exe = serverOn mainProjectOut dummyVersion;
server = args@{ hostName, adminEmail, routeHost, enableHttps, version }:
server = args@{ hostName, adminEmail, routeHost, enableHttps, version, module ? serverModules.mkBaseEc2 }:
server (args // { exe = linuxExe version; });
obelisk = import (base' + "/.obelisk/impl") {};
};
Expand Down
4 changes: 2 additions & 2 deletions lib/command/src/Obelisk/Command.hs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ deployCommand cfg = hsubparser $ mconcat

deployInitOpts :: Parser DeployInitOpts
deployInitOpts = DeployInitOpts
<$> strArgument (action "directory" <> metavar "DEPLOYDIR" <> help "Path to a directory that it will create")
<*> strOption (long "ssh-key" <> action "file" <> metavar "SSHKEY" <> help "Path to an ssh key that it will symlink to")
<$> strArgument (action "directory" <> metavar "DEPLOYDIR" <> help "Path to a directory where the deployment repository will be initialized")
<*> strOption (long "ssh-key" <> action "file" <> metavar "SSHKEY" <> help "Path to an SSH key that will be *copied* to the deployment repository")
<*> some (strOption (long "hostname" <> metavar "HOSTNAME" <> help "hostname of the deployment target"))
<*> strOption (long "route" <> metavar "PUBLICROUTE" <> help "Publicly accessible URL of your app")
<*> strOption (long "admin-email" <> metavar "ADMINEMAIL" <> help "Email address where administrative alerts will be sent")
Expand Down
62 changes: 36 additions & 26 deletions lib/command/src/Obelisk/Command/Deploy.hs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ deployInit
-> Bool -- ^ enable https
-> m ()
deployInit thunkPtr deployDir sshKeyPath hostnames route adminEmail enableHttps = do
liftIO $ createDirectoryIfMissing True deployDir
localKey <- withSpinner ("Preparing " <> T.pack deployDir) $ do
localKey <- liftIO (doesFileExist sshKeyPath) >>= \case
False -> failWith $ T.pack $ "ob deploy init: file does not exist: " <> sshKeyPath
Expand All @@ -74,22 +75,30 @@ deployInit thunkPtr deployDir sshKeyPath hostnames route adminEmail enableHttps
, deployDir </> "config" </> "common"
, deployDir </> "config" </> "frontend"
]

let srcDir = deployDir </> "src"
withSpinner ("Creating source thunk (" <> T.pack (makeRelative deployDir srcDir) <> ")") $ liftIO $ do
createThunk srcDir thunkPtr
setupObeliskImpl deployDir

withSpinner "Writing deployment configuration" $ do
writeDeployConfig deployDir "backend_hosts" $ unlines hostnames
writeDeployConfig deployDir "enable_https" $ show enableHttps
writeDeployConfig deployDir "admin_email" adminEmail
writeDeployConfig deployDir ("config" </> "common" </> "route") route
withSpinner "Creating source thunk (./src)" $ liftIO $ do
createThunk (deployDir </> "src") thunkPtr
setupObeliskImpl deployDir
writeDeployConfig deployDir "module.nix" $
"(import " <> toNixPath (makeRelative deployDir srcDir) <> " {}).obelisk.serverModules.mkBaseEc2"

withSpinner ("Initializing git repository (" <> T.pack deployDir <> ")") $
initGit deployDir

setupObeliskImpl :: FilePath -> IO ()
setupObeliskImpl deployDir = do
let implDir = toImplDir deployDir
let
implDir = toImplDir deployDir
goBackUp = foldr (</>) "" $ (".." <$) $ splitPath $ makeRelative deployDir implDir
createDirectoryIfMissing True implDir
writeFile (implDir </> "default.nix") "(import ../../src {}).obelisk"
writeFile (implDir </> "default.nix") $ "(import " <> toNixPath (goBackUp </> "src") <> " {}).obelisk"

deployPush :: MonadObelisk m => FilePath -> m [String] -> m ()
deployPush deployPath getNixBuilders = do
Expand All @@ -108,6 +117,8 @@ deployPush deployPath getNixBuilders = do
Left err -> failWith $ "ob deploy push: couldn't read src thunk: " <> T.pack (show err)
let version = show . _thunkRev_commit $ _thunkPtr_rev thunkPtr
builders <- getNixBuilders
let moduleFile = deployPath </> "module.nix"
moduleFileExists <- liftIO $ doesFileExist moduleFile
buildOutputByHost <- ifor (Map.fromSet (const ()) hosts) $ \host () -> do
--TODO: What does it mean if this returns more or less than 1 line of output?
[result] <- fmap lines $ nixCmd $ NixCmd_Build $ def
Expand All @@ -117,13 +128,13 @@ deployPush deployPath getNixBuilders = do
, _target_expr = Nothing
}
& nixBuildConfig_outLink .~ OutLink_None
& nixCmdConfig_args .~
& nixCmdConfig_args .~ (
[ strArg "hostName" host
, strArg "adminEmail" adminEmail
, strArg "routeHost" routeHost
, strArg "version" version
, boolArg "enableHttps" enableHttps
]
] <> [rawArg "module" ("import " <> toNixPath moduleFile) | moduleFileExists ])
& nixCmdConfig_builders .~ builders
pure result
let knownHostsPath = deployPath </> "backend_known_hosts"
Expand Down Expand Up @@ -207,12 +218,14 @@ deployMobile platform mobileArgs = withProjectRoot "." $ \root -> do
keytoolConfContents <- liftIO $ BSL.readFile keytoolConfPath
keyArgs <- case eitherDecode keytoolConfContents :: Either String KeytoolConfig of
Left err -> failWith $ T.pack err
Right conf -> pure [ "--sign"
, "--store-file", _keytoolConfig_keystore conf
, "--store-password", _keytoolConfig_storepass conf
, "--key-alias", _keytoolConfig_alias conf
, "--key-password", _keytoolConfig_keypass conf]
let expr = unwords
Right conf -> pure
[ "--sign"
, "--store-file", _keytoolConfig_keystore conf
, "--store-password", _keytoolConfig_storepass conf
, "--key-alias", _keytoolConfig_alias conf
, "--key-password", _keytoolConfig_keypass conf
]
let expr = mconcat
[ "with (import ", toNixPath srcDir, " {});"
, "android.frontend.override (drv: {"
, "isRelease = true;"
Expand Down Expand Up @@ -264,19 +277,16 @@ instance FromJSON KeytoolConfig
instance ToJSON KeytoolConfig

createKeystore :: MonadObelisk m => FilePath -> KeytoolConfig -> m ()
createKeystore root config = do
let expr = "with (import " <> toImplDir root <> ").reflex-platform.nixpkgs; pkgs.mkShell { buildInputs = [ pkgs.jdk ]; }"
callProcessAndLogOutput (Notice,Notice) $ setCwd (Just root) $ proc "nix-shell" ["-E" , expr, "--run" , keytoolCmd]
where
keytoolCmd = processToShellString "keytool"
[ "-genkeypair", "-noprompt"
, "-keystore", _keytoolConfig_keystore config
, "-keyalg", "RSA", "-keysize", "2048"
, "-validity", "1000000"
, "-storepass", _keytoolConfig_storepass config
, "-alias", _keytoolConfig_alias config
, "-keypass", _keytoolConfig_keypass config
]
createKeystore root config =
callProcessAndLogOutput (Notice, Notice) $ setCwd (Just root) $ proc jreKeyToolPath
[ "-genkeypair", "-noprompt"
, "-keystore", _keytoolConfig_keystore config
, "-keyalg", "RSA", "-keysize", "2048"
, "-validity", "1000000"
, "-storepass", _keytoolConfig_storepass config
, "-alias", _keytoolConfig_alias config
, "-keypass", _keytoolConfig_keypass config
]

-- | Simplified deployment configuration mechanism. At one point we may revisit this.
writeDeployConfig :: MonadObelisk m => FilePath -> FilePath -> String -> m ()
Expand Down
3 changes: 3 additions & 0 deletions lib/command/src/Obelisk/Command/Utils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ nixExePath = $(staticWhich "nix")
nixBuildExePath :: FilePath
nixBuildExePath = $(staticWhich "nix-build")

jreKeyToolPath :: FilePath
jreKeyToolPath = $(staticWhich "keytool")

-- Check whether the working directory is clean
checkGitCleanStatus :: MonadObelisk m => FilePath -> Bool -> m Bool
checkGitCleanStatus repo withIgnored = do
Expand Down