git.iko.soy/typeable/octopod
on master help sign in
Improved handling metadata links (#9)

* Added link titles * Preserve metadata ordering * Added dev run commands to Makefile * Added explanation to the development guide * Made caddy 2 the only config since we probably don't want to maintain two versions. * Use `id` instead of `ord` * Added ghcid version of Caddyfile * Renamed staging to deployment

commit9bcfdcf065a76706c9c4ea95c74aaaf868a182a2 authoriko <ilyakooo0@gmail.com> dateTue 12 Jan 2021, 11:47 (6 years ago) parentef65a9b
16 files changed+172−89
.gitignore+1 Caddyfile+27−1 Caddyfile1−42 Caddyfile2 → CaddyfileGhcid+5−7 Development_guide.md+29−2 Makefile+16−1 dev/config.json+5 dev/dev_backend.sh+29 dev/echo.sh+4 dev/info.sh+6 octopod-backend/src/Octopod/Server.hs+19−16 octopod-common/src/Common/Types.hs+2−2 octopod-frontend/octopod-frontend.cabal+1 octopod-frontend/src/Page/Deployment.hs+3−9 octopod-frontend/src/Page/Deployments.hs+2−9 octopod-frontend/src/Page/Elements/Links.hs+23
.gitignore+1
@@ -7,3 +7,4 @@ dist-newstyle
77octopod-config.json
88frontend-result
99octopod-css/node_modules
10+dev/certs/*
Caddyfilemode 120000 → 100644+27−1
@@ -1 +0,0 @@
1−Caddyfile2
@@ -0,0 +1,27 @@
1+http://localhost:8000
2+
3+
4+file_server
5+
6+rewrite /api/* {path}
7+reverse_proxy /api/* localhost:3002
8+
9+@production {
10+ path_regexp production /static/(.*)
11+}
12+rewrite @production /octopod-css/production/{http.regexp.production.1}
13+
14+rewrite /config.json /dev/config.json
15+rewrite * /frontend-result/bin/frontend.jsexe/{path}
16+
17+
18+@3003 {
19+ not path /octopod-css/* /frontend-result/* /dev/config.json
20+}
21+
22+reverse_proxy @3003 localhost:3003
23+
24+log {
25+ output stdout
26+ format single_field common_log
27+}
Caddyfile1deleted−42
@@ -1,42 +0,0 @@
1−localhost:8000
2−
3−mime .css text/css
4−
5−rewrite /static/styles {
6− r (.*)
7− to /octopod-css/production/styles/{1}
8−}
9−
10−mime .js application/javascript
11−
12−rewrite /static/vendors {
13− r (.*)
14− to /octopod-css/production/vendors/{1}
15−}
16−
17−rewrite /static/scripts {
18− r (.*)
19− to /octopod-css/production/scripts/{1}
20−}
21−
22−rewrite /static/images {
23− r (.*)
24− to /octopod-css/production/images/{1}
25−}
26−
27−proxy /api localhost:3002 {
28− transparent
29−}
30−
31−mime .json application/json
32−rewrite /config.json /octopod-config.json
33−
34−rewrite /ghcjs /frontend-result/bin/frontend.jsexe/index.html
35−
36−proxy / localhost:3003 {
37− transparent
38− websocket
39− except /octopod-css/ /frontend-result/ /octopod-config.json
40−}
41−
42−log / stdout
Caddyfile2 → CaddyfileGhcidrenamed+5−7
@@ -3,20 +3,18 @@ http://localhost:8000
33
44file_server
55
6+rewrite /api/* {path}
7+reverse_proxy /api/* localhost:3002
8+
69@production {
710 path_regexp production /static/(.*)
811}
9−
1012rewrite @production /octopod-css/production/{http.regexp.production.1}
1113
12−reverse_proxy /api localhost:3002
13−
14−rewrite /config.json /octopod-config.json
15−
16−rewrite /ghcjs /frontend-result/bin/frontend.jsexe/index.html
14+rewrite /config.json /dev/config.json
1715
1816@3003 {
19− not path /octopod-css/* /frontend-result/* /octopod-config.json
17+ not path /octopod-css/* /frontend-result/* /dev/config.json
2018}
2119
2220reverse_proxy @3003 localhost:3003
Development_guide.md+29−2
@@ -64,9 +64,36 @@ For development, we have set up `ghcid` commands that rebuild the project every
6464- `ghcid-cli`
6565- `ghcid-frontend`
6666
67−### Frontend proxy
67+### Running the project locally
6868
69−The frontend should be accessed through a proxy. We have set up [caddy](https://caddyserver.com) configs to ease development. You will need place an `octopod-config.json` file at the root of the repository containing a [config](../../charts/octopod/templates/octopod-nginx-configmap.yaml#L15-L20). `app_auth` can be an arbitrary string – it will not affect anything when running locally.
69+We have two commands to run the backend and frontend in the `Makefile`:
70+
71+- `run-backend-dev`
72+
73+ Builds a production version of the backend server, runs database migrations and starts the server with mock control scripts. You can see the used config in [`dev/dev_backend.sh`](./dev/dev_backend.sh).
74+
75+ ### NOTE:
76+
77+ 1. You need to have a [Postgres](https://www.postgresql.org) database running on `localhost:5432` (the default port).
78+
79+ 2. You need to create an empty `octopod` database.
80+
81+ 3. You also need to have an `octopod:octopod` user set up as it will be used by the server to access the database.
82+
83+ The easiest way to do this is by running the following command after you have _Postgres running_:
84+ ```bash
85+ psql -c "CREATE ROLE IF NOT EXISTS octopod WITH PASSWORD 'octopod' SUPERUSER LOGIN;"
86+ ```
87+
88+ 4. You will need [sqitch](https://sqitch.org) installed on your system as it will be used to run migrations on the database.
89+
90+- `run-frontend-dev`
91+
92+ Build a production version of the frontend and runs it locally, pointing it to the locally running backend server.
93+
94+ ### NOTE:
95+
96+ You need to have [_Caddy 2_](https://caddyserver.com/v2) installed on your system as it is automatically used as a proxy.
7097
7198### Stack
7299
Makefile+16−1
@@ -1,4 +1,4 @@
1−.PHONY: build-backend build-octo-cli build-frontend docs backend-docs frontend-docs repl shell shell-ghcjs ghcid ghcid-cli ghcid-frontend push-octopod
1+.PHONY: build-backend build-octo-cli build-frontend docs backend-docs frontend-docs repl shell shell-ghcjs ghcid ghcid-cli ghcid-frontend push-octopod run-backend-dev run-frontend-dev
22
33build-backend:
44 nix-build . -A ghc.octopod-backend
@@ -37,3 +37,18 @@ ghcid-frontend:
3737
3838push-octopod:
3939 ./build.sh build-and-push latest
40+
41+run-backend-dev: dev/certs/server_cert.pem dev/certs/server_key.pem
42+ ./dev/dev_backend.sh `nix-build . -A ghc.octopod-backend`
43+
44+run-frontend-dev: build-frontend
45+ caddy run
46+
47+dev/certs/server_cert.pem dev/certs/server_key.pem:
48+ openssl req -x509 -newkey rsa:4096 -keyout dev/certs/server_key.pem -out dev/certs/server_cert.pem -nodes -subj "/CN=localhost/O=Server"
49+
50+dev/certs/client_csr.pem dev/certs/client_key.pem:
51+ openssl req -newkey rsa:4096 -keyout dev/certs/client_key.pem -out dev/certs/client_csr.pem -nodes -subj "/CN=Client"
52+
53+dev/certs/client_cert.pem: dev/certs/client_csr.pem dev/certs/server_cert.pem dev/certs/server_key.pem
54+ openssl x509 -req -in dev/certs/client_csr.pem -CA dev/certs/server_cert.pem -CAkey dev/certs/server_key.pem -out dev/certs/client_cert.pem -set_serial 01 -days 3650)
dev/config.jsonnew file+5
@@ -0,0 +1,5 @@
1+{
2+ "app_url": "http://localhost:8000",
3+ "ws_url": "ws://localhost:4020",
4+ "app_auth": ""
5+}
dev/dev_backend.shnew file+29
@@ -0,0 +1,29 @@
1+#!/bin/bash
2+
3+(cd migrations && sqitch deploy -t postgresql://octopod:octopod@localhost:5432/octopod)
4+
5+export MOUNT_DIR=./dev
6+
7+export PROJECT_NAME="Example Project"
8+export BASE_DOMAIN=octopod.example.com
9+export NAMESPACE=deployment
10+export ARCHIVE_RETENTION=60
11+export STATUS_UPDATE_TIMEOUT=600
12+export CREATION_COMMAND=$MOUNT_DIR/echo.sh
13+export UPDATE_COMMAND=$MOUNT_DIR/echo.sh
14+export UPDATE_ENVS_COMMAND=$MOUNT_DIR/echo.sh
15+export ARCHIVE_COMMAND=$MOUNT_DIR/echo.sh
16+export CHECKING_COMMAND=$MOUNT_DIR/echo.sh
17+export CLEANUP_COMMAND=$MOUNT_DIR/echo.sh
18+export ARCHIVE_CHECKING_COMMAND=$MOUNT_DIR/echo.sh
19+export TAG_CHECKING_COMMAND=$MOUNT_DIR/echo.sh
20+export INFO_COMMAND=$MOUNT_DIR/info.sh
21+$1/bin/octopod-exe \
22+ --port 4443 \
23+ --ui-port 3002 \
24+ --ws-port 4020 \
25+ --db "host='127.0.0.1' port=5432 user='octopod' password='octopod'" \
26+ --db-pool-size 10 \
27+ --tls-cert-path dev/certs/server_cert.pem \
28+ --tls-key-path dev/certs/server_key.pem \
29+ --tls-store-path /tmp/tls_store
dev/echo.shnew file+4
@@ -0,0 +1,4 @@
1+#!/bin/bash
2+
3+echo $0 $@
4+exit 0
dev/info.shnew file+6
@@ -0,0 +1,6 @@
1+#!/bin/bash
2+
3+echo "key,value"
4+echo "key2,value2"
5+
6+exit 0
octopod-backend/src/Octopod/Server.hs+19−16
@@ -41,6 +41,7 @@ import System.Process.Typed
4141
4242import Common.Utils
4343import Common.Validation (isNameValid)
44+import Database.PostgreSQL.Simple.Transaction
4445import Octopod.API
4546import Octopod.PowerAPI
4647import Octopod.Server.Args
@@ -385,8 +386,7 @@ updateDeploymentInfo dName st = do
385386 case ec of
386387 ExitSuccess -> do
387388 dMeta <- parseDeploymentMetadata (lines . unStdout $ out)
388− forM_ dMeta $ \meta ->
389− upsertDeploymentMetadata (pool st) dName meta
389+ upsertDeploymentMetadata (pool st) dName dMeta
390390 ExitFailure _ ->
391391 log $
392392 "could not get deployment info, exit code: " <> (pack . show $ ec)
@@ -1045,7 +1045,8 @@ selectDeploymentMetadata conn dName = do
10451045 let
10461046 q =
10471047 "SELECT key, value FROM deployment_metadata \
1048− \WHERE deployment_id = (SELECT id FROM deployments WHERE name = ?)"
1048+ \WHERE deployment_id = (SELECT id FROM deployments WHERE name = ?) \
1049+ \ORDER BY id ASC"
10491050 rows <- query conn q (Only dName)
10501051 for rows $ \(k, v) -> pure $ DeploymentMetadata k v
10511052
@@ -1065,21 +1066,23 @@ deleteDeploymentMetadata pgPool dName = do
10651066upsertDeploymentMetadata
10661067 :: PgPool
10671068 -> DeploymentName
1068− -> DeploymentMetadata
1069+ -> [DeploymentMetadata]
10691070 -> IO ()
1070−upsertDeploymentMetadata pgPool dName dMetadata = do
1071+upsertDeploymentMetadata pgPool dName dMetadatas = do
10711072 let
1072− q =
1073− "INSERT INTO deployment_metadata \
1074− \(deployment_id, key, value, created_at, updated_at) \
1075− \(SELECT id, ?, ?, now(), now() FROM deployments WHERE name = ?) \
1076− \ON CONFLICT (deployment_id, key) \
1077− \DO \
1078− \UPDATE SET key = ?, value = ?, updated_at = now()"
1079− key = deploymentMetadataKey dMetadata
1080− value = deploymentMetadataValue dMetadata
1081− void $ withResource pgPool $ \conn ->
1082− execute conn q (key, value, dName, key, value)
1073+ withResource pgPool $ \conn -> withTransactionSerializable conn $ do
1074+ void $ execute
1075+ conn
1076+ "DELETE FROM deployment_metadata \
1077+ \WHERE deployment_id = (SELECT id FROM deployments WHERE name = ?)"
1078+ (Only dName)
1079+ forM_ dMetadatas $ \dMeta ->
1080+ execute
1081+ conn
1082+ "INSERT INTO deployment_metadata \
1083+ \(deployment_id, key, value, created_at, updated_at) \
1084+ \(SELECT id, ?, ?, now(), now() FROM deployments WHERE name = ?)"
1085+ (deploymentMetadataKey dMeta, deploymentMetadataValue dMeta, dName)
10831086
10841087-- | Checks the existence of a deployment tag.
10851088-- Returns 404 'Tag not found' response if the deployment tag doesn't exist.
octopod-common/src/Common/Types.hs+2−2
@@ -117,8 +117,8 @@ data DeploymentLog = DeploymentLog
117117 deriving (FromJSON, ToJSON) via Snake DeploymentLog
118118
119119data DeploymentMetadata = DeploymentMetadata
120− { deploymentMetadataKey :: Text
121− , deploymentMetadataValue :: Text
120+ { deploymentMetadataKey :: Text -- ^ The name of the link
121+ , deploymentMetadataValue :: Text -- ^ The URL
122122 }
123123 deriving (Generic, Show, Eq)
124124 deriving (FromJSON, ToJSON) via Snake DeploymentMetadata
octopod-frontend/octopod-frontend.cabal+1
@@ -33,6 +33,7 @@ executable frontend
3333 , Page.Popup.EditDeployment
3434 , Page.Popup.NewDeployment
3535 , Servant.Reflex.Extra
36+ , Page.Elements.Links
3637 ghc-options: -Wall
3738 -Werror
3839 -Wno-missing-home-modules
octopod-frontend/src/Page/Deployment.hs+3−9
@@ -22,6 +22,7 @@ import Frontend.API
2222import Frontend.Route
2323import Frontend.Utils
2424import Page.ClassicPopup
25+import Page.Elements.Links
2526import Page.Popup.EditDeployment
2627
2728-- | The root widget of a deployment page. It requests the deployment data.
@@ -68,7 +69,7 @@ deploymentWidget updEv dfi = mdo
6869 [ DPMError "Couldn't update status of deployment" <$ errEv
6970 , DPMClear <$ okEv ]
7071 deploymentBody updEv dfiDyn
71− pure (editEv')
72+ pure editEv'
7273 sentEv <- editDeploymentPopup editEv never
7374 blank
7475
@@ -161,14 +162,7 @@ deploymentBody updEv dfiDyn = deploymentBodyWrapper $ do
161162 elClass "h3" "deployment__sub-heading" $ text "Links"
162163 divClass "deployment__widget" $
163164 divClass "listing" $
164− void $ simpleList urlsDyn $ \urlDyn' -> do
165− let
166− urlDyn = deploymentMetadataValue <$> urlDyn'
167− attrDyn = urlDyn <&> \url ->
168− ( "class" =: "listing__item external bar bar--larger"
169− <> "href" =: url
170− <> "target" =: "_blank" )
171− elDynAttr "a" attrDyn $ dynText urlDyn
165+ void $ simpleList urlsDyn renderMetadataLink
172166 elClass "section" "deployment__section" $ do
173167 let
174168 envsDyn = dfiDyn <^.> field @"deployment"
octopod-frontend/src/Page/Deployments.hs+2−9
@@ -29,6 +29,7 @@ import Frontend.API
2929import Frontend.Route
3030import Frontend.Utils
3131import Page.ClassicPopup
32+import Page.Elements.Links
3233import Page.Popup.EditDeployment
3334import Page.Popup.NewDeployment
3435
@@ -239,15 +240,7 @@ activeDeploymentWidget clickedEv dDyn' = do
239240 el "td" $ do
240241 text $ coerce dname
241242 statusWidget $ constDyn status
242− el "td" $ do
243− divClass "listing" $
244− forM_ metadata $ \meta -> do
245− let
246− url = deploymentMetadataValue meta
247− void $ elAttr' "a"
248− ( "class" =: "listing__item external bar"
249− <> "href" =: url
250− <> "target" =: "_blank") $ text url
243+ el "td" $ divClass "listing" $ forM_ metadata (renderMetadataLink . pure)
251244 el "td" $
252245 text $ coerce $ deployment ^. field @"tag"
253246 el "td" $
octopod-frontend/src/Page/Elements/Links.hsnew file+23
@@ -0,0 +1,23 @@
1+module Page.Elements.Links
2+ ( renderMetadataLink
3+ ) where
4+
5+import Common.Types
6+import Data.Functor
7+import qualified Data.Text as T
8+import Reflex.Dom
9+
10+renderMetadataLink
11+ :: (DomBuilder t m, PostBuild t m)
12+ => Dynamic t DeploymentMetadata -> m ()
13+renderMetadataLink metadataD = do
14+ let
15+ attrDyn = metadataD <&> \metadata ->
16+ "class" =: "listing__item external bar bar--larger"
17+ <> "href" =: deploymentMetadataValue metadata
18+ <> "target" =: "_blank"
19+ elDynAttr "a" attrDyn . dynText $ metadataD <&> \case
20+ -- If the name is empty, then use the url
21+ DeploymentMetadata {deploymentMetadataKey = name}
22+ | (not . T.null . T.strip) name -> name
23+ DeploymentMetadata {deploymentMetadataValue = url} -> url