{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}

module Kerry.Provisioner.Shell (
  -- * Shell provisioner
    Shell(..)
  , shell

  , ShellType(..)

  -- ** Serialziation
  , fromShell
  ) where

import           Data.Aeson ((.=))
import qualified Data.Aeson.Types as Aeson

import           Kerry.Internal.Prelude
import           Kerry.Internal.Serial


-- |
-- The shell Packer provisioner provisions machines built by Packer
-- using shell scripts. Shell provisioning is the easiest way to get
-- software installed and configured on a machine.
--
data Shell =
  Shell {
      shellType :: ShellType
    -- | If true, specifies that the script(s) are binary files, and Packer should therefore not convert Windows line endings to Unix line endings (if there are any). By default this is false.
    , shellBinary :: Maybe Bool
    -- | Valid exit codes for the script. By default this is just 0.
    , shellValidExitCodes :: Maybe [Int]
    -- | An array of key/value pairs to inject prior to the execute_command. The format should be key=value. Packer injects some environmental variables by default into the environment, as well, which are covered in the section below.
    , shellEnvironmentVars :: Maybe [Text]
    -- | If true, Packer will write your environment variables to a tempfile and source them from that file, rather than declaring them inline in our execute_command. The default execute_command will be chmod +x {{.Path}}; . {{.EnvVarFile}} && {{.Path}}. This option is unnecessary for most cases, but if you have extra quoting in your custom execute_command, then this may be unnecessary for proper script execution. Default: false.
    , shellUseEnvVarFile :: Maybe Bool
    -- | The command to use to execute the script. By default this is chmod +x {{ .Path }}; {{ .Vars }} {{ .Path }}, unless the user has set "use_env_var_file": true -- in that case, the default execute_command is chmod +x {{.Path}}; . {{.EnvVarFile}} && {{.Path}}. The value of this is treated as a configuration template. There are three available variables:
    --
    -- Path is the path to the script to run
    -- Vars is the list of environment_vars, if configured.
    -- EnvVarFile is the path to the file containing env vars, if use_env_var_file is true.
    , shellExecuteCommand :: Maybe Text
    -- | Defaults to false. Whether to error if the server disconnects us. A disconnect might happen if you restart the ssh server or reboot the host.
    , shellExpectDisconnect :: Maybe Bool
    -- | The shebang value to use when running commands specified by inline. By default, this is /bin/sh -e. If you're not using inline, then this configuration has no effect. Important: If you customize this, be sure to include something like the -e flag, otherwise individual steps failing won't fail the provisioner.
    , shellInlineShebang :: Maybe Text
    -- | The folder where the uploaded script will reside on the machine. This defaults to '/tmp'.
    , shellRemoteFolder :: Maybe FilePath
    -- | The filename the uploaded script will have on the machine. This defaults to 'script_nnn.sh'.
    , shellRemoteFile :: Maybe Text
    -- | The full path to the uploaded script will have on the machine. By default this is remote_folder/remote_file, if set this option will override both remote_folder and remote_file.
    , shellRemotePath :: Maybe FilePath
    -- | If true, specifies that the helper scripts uploaded to the system will not be removed by Packer. This defaults to false (clean scripts from the system).
    , shellSkipClean :: Maybe Bool
    -- | The amount of time to attempt to start the remote process. By default this is 5m or 5 minutes. This setting exists in order to deal with times when SSH may restart, such as a system reboot. Set this to a higher value if reboots take a longer amount of time.
    , shellStartRetryTimeout :: Maybe Text
    -- | Wait the amount of time after provisioning a shell script, this pause be taken if all previous steps were successful.
    , shellPauseAfter :: Maybe Text
    } deriving (Eq, Show)

-- | Basic 'Shell'
shell :: ShellType -> Shell
shell st =
    Shell {
      shellType = st
    , shellBinary = Nothing
    , shellValidExitCodes = Nothing
    , shellEnvironmentVars = Nothing
    , shellUseEnvVarFile = Nothing
    , shellExecuteCommand = Nothing
    , shellExpectDisconnect = Nothing
    , shellInlineShebang = Nothing
    , shellRemoteFolder = Nothing
    , shellRemoteFile = Nothing
    , shellRemotePath = Nothing
    , shellSkipClean = Nothing
    , shellStartRetryTimeout = Nothing
    , shellPauseAfter = Nothing
    }


-- | 'ShellType'
data ShellType =
    Inline [Text]
  | Script Text
  | Scripts [Text]
    deriving (Eq, Show)


-- | Shell serialization
fromShell :: Shell -> [Aeson.Pair]
fromShell s =
  join [[
    "type" .= t "shell"
  , fromShellType (shellType s)
  ]
  , "binary" .=? (shellBinary s)
  , "valid_exit_codes" .=? (shellValidExitCodes s)
  , "environment_vars" .=? (shellEnvironmentVars s)
  , "use_env_var_file" .=? (shellUseEnvVarFile s)
  , "execute_command" .=? (shellExecuteCommand s)
  , "expect_disconnect" .=? (shellExpectDisconnect s)
  , "inline_shebang" .=? (shellInlineShebang s)
  , "remote_folder" .=? (shellRemoteFolder s)
  , "remote_file" .=? (shellRemoteFile s)
  , "remote_path" .=? (shellRemotePath s)
  , "skip_clean" .=? (shellSkipClean s)
  , "start_retry_timeout" .=? (shellStartRetryTimeout s)
  , "pause_after" .=? (shellPauseAfter s)
  ]

fromShellType :: ShellType -> Aeson.Pair
fromShellType = \case
  Inline xs ->
    "inline" .= xs
  Script x ->
    "script" .= x
  Scripts xs ->
    "scripts" .= xs