Skip to content

Commit fd48873

Browse files
committed
[cli] internal: add github copy directrory functionality
1 parent a71d7e9 commit fd48873

File tree

7 files changed

+277
-6
lines changed

7 files changed

+277
-6
lines changed

cli/anti.cabal

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,39 @@ library test-lib
449449

450450
-- Base language which the package is written in.
451451
default-language: Haskell2010
452+
453+
test-suite anti-integration-test
454+
-- Import common warning flags.
455+
import: warnings
456+
457+
-- Base language which the package is written in.
458+
default-language: Haskell2010
459+
460+
-- Modules included in this executable, other than Main.
461+
other-modules:
462+
Lib.GitHubSpec
463+
464+
-- LANGUAGE extensions used by modules in this package.
465+
-- other-extensions:
466+
467+
-- The interface type and version of the test suite.
468+
type: exitcode-stdio-1.0
469+
470+
-- Directories containing source files.
471+
hs-source-dirs: test-integration
472+
473+
-- The entrypoint to the test suite.
474+
main-is: Main.hs
475+
476+
-- Test dependencies.
477+
build-depends:
478+
, anti
479+
, base
480+
, bytestring
481+
, directory
482+
, hspec
483+
, github
484+
, path
485+
, temporary
486+
487+
build-tool-depends: hspec-discover:hspec-discover

cli/justfile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ format:
77
#!/usr/bin/env bash
88
# shellcheck disable=SC2034
99
for i in {1..3}; do
10-
fourmolu -i src app test test-E2E test-lib CI/rewrite-libs
10+
fourmolu -i src app test test-E2E test-integration test-lib CI/rewrite-libs
1111
done
1212
cabal-fmt -i anti.cabal CI/rewrite-libs/rewrite-libs.cabal
1313
nixfmt *.nix
@@ -36,6 +36,18 @@ build:
3636
#!/usr/bin/env bash
3737
cabal build all --enable-tests
3838

39+
integration match="":
40+
#!/usr/bin/env bash
41+
# shellcheck disable=SC2050
42+
if [[ '{{ match }}' == "" ]]; then
43+
cabal test anti-integration-test \
44+
--test-show-details=direct
45+
else
46+
cabal test anti-integration-test \
47+
--test-show-details=direct \
48+
--test-option=--match \
49+
--test-option="{{ match }}"
50+
fi
3951
E2E match="":
4052
#!/usr/bin/env bash
4153
mkdir -p tmp/bin
@@ -121,6 +133,7 @@ CI:
121133
set -euo pipefail
122134
just build
123135
just unit
136+
just integration
124137
just E2E
125138
cabal-fmt -c anti.cabal CI/rewrite-libs/rewrite-libs.cabal
126139
fourmolu -m check src app test CI/rewrite-libs

cli/src/Lib/GitHub.hs

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,32 @@ module Lib.GitHub
88
, githubGetFile
99
, githubGetCodeOwnersFile
1010
, githubRepositoryExists
11+
12+
-- * Utilities for working with GitHub directories
13+
, copyGithubDirectory
14+
, writeToDirectory
15+
, exitOnException
16+
, githubStreamDirectoryContents
1117
) where
1218

1319
import Control.Exception
1420
( Exception
21+
, SomeException
1522
)
23+
import Control.Monad.Fix (fix)
24+
import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
1625
import Core.Types.Basic
1726
( Commit (..)
1827
, Directory (..)
1928
, FileName (..)
2029
, Repository (..)
2130
, Username (..)
2231
)
32+
import Data.ByteString (ByteString)
33+
import Data.ByteString qualified as B
2334
import Data.ByteString.Base64 qualified as B64
24-
import Data.Foldable (Foldable (..))
35+
import Data.Foldable (Foldable (..), forM_)
36+
import Data.Function ((&))
2537
import Data.Text qualified as T
2638
import Data.Text.Encoding qualified as T
2739
import GitHub (Auth (..), FetchCount (..), github)
@@ -34,7 +46,22 @@ import Network.HTTP.Client
3446
, Response (..)
3547
)
3648
import Network.HTTP.Types (Status (..))
49+
import Path
50+
( Abs
51+
, Dir
52+
, File
53+
, Path
54+
, Rel
55+
56+
, parseRelFile
57+
, toFilePath
58+
, (</>), parseRelDir, parent
59+
)
60+
import Streaming
61+
import Streaming.Prelude (yield)
62+
import Streaming.Prelude qualified as S
3763
import Text.JSON.Canonical
64+
import System.Directory (createDirectoryIfMissing)
3865

3966
data GithubResponseError
4067
= GithubResponseErrorRepositoryNotFound
@@ -179,6 +206,7 @@ data GetGithubFileFailure
179206
| GetGithubFileUnsupportedEncoding String
180207
| GetGithubFileOtherFailure FilePath String
181208
| GetGithubFileCodeError GithubResponseStatusCodeError
209+
| GithubPathParsingError String
182210
deriving (Eq, Show)
183211

184212
instance Exception GetGithubFileFailure
@@ -237,3 +265,118 @@ githubGetFile auth (Repository owner repo) commitM (FileName filename) = do
237265
where
238266
owner' = N $ T.pack owner
239267
repo' = N $ T.pack repo
268+
269+
githubStreamDirectoryContents
270+
:: Auth
271+
-> Repository
272+
-> Maybe Commit
273+
-> Path Rel Dir
274+
-> Stream
275+
(Of (Path Rel File, ByteString))
276+
(ExceptT GetGithubFileFailure IO)
277+
()
278+
githubStreamDirectoryContents
279+
auth
280+
(Repository owner repo)
281+
commitM
282+
startDir =
283+
($ startDir) $ fix $ \go dir -> do
284+
-- Get the contents of the source directory
285+
response <-
286+
liftIO
287+
$ github auth
288+
$ GH.contentsForR
289+
owner'
290+
repo'
291+
(T.dropEnd 1 $ T.pack $ toFilePath dir)
292+
((\(Commit c) -> T.pack c) <$> commitM)
293+
case response of
294+
Left e -> do
295+
res <- liftIO $ onStatusCodeOfException e $ \c -> do
296+
case c of
297+
404 ->
298+
pure
299+
. Just
300+
$ GetGithubFileDirectoryNotFound
301+
_ ->
302+
pure
303+
. Just
304+
. GetGithubFileOtherFailure (toFilePath dir)
305+
$ show e
306+
case res of
307+
Left err -> lift $ throwE $ GetGithubFileCodeError err
308+
Right a -> lift $ throwE a
309+
Right (GH.ContentFile contents) -> do
310+
let content = GH.contentFileContent contents
311+
ebytes <- case GH.contentFileEncoding contents of
312+
"base64" ->
313+
pure
314+
. Right
315+
. B64.decodeLenient
316+
. T.encodeUtf8
317+
$ content
318+
enc ->
319+
pure
320+
. Left
321+
. GetGithubFileUnsupportedEncoding
322+
$ T.unpack enc
323+
case ebytes of
324+
Left err -> lift $ throwE err
325+
Right t -> lift (dirToFile dir) >>= yield . (,t)
326+
Right (GH.ContentDirectory vis) -> forM_ vis $ \case
327+
GH.ContentItem _ctype GH.ContentInfo{contentPath} -> do
328+
dir' <-
329+
lift
330+
$ throwPathParsing
331+
$ parseRelDir (T.unpack contentPath)
332+
go dir'
333+
where
334+
owner' = N $ T.pack owner
335+
repo' = N $ T.pack repo
336+
337+
dirToFile
338+
:: Monad m => Path b t -> ExceptT GetGithubFileFailure m (Path Rel File)
339+
dirToFile = throwPathParsing . parseRelFile . tailU . toFilePath
340+
where
341+
tailU = T.unpack . T.dropWhileEnd (== '/') . T.dropWhile (== '/') . T.pack
342+
343+
throwPathParsing
344+
:: (Monad m)
345+
=> Either SomeException a
346+
-> ExceptT GetGithubFileFailure m a
347+
throwPathParsing f = case f of
348+
Left err -> throwE . GithubPathParsingError . show $ err
349+
Right file -> pure file
350+
351+
writeToDirectory
352+
:: Path Abs Dir -> Stream (Of (Path Rel File, ByteString)) IO r -> IO r
353+
writeToDirectory targetDir = S.mapM_ writeFile'
354+
where
355+
writeFile' (relPath, content) = do
356+
let fullPath = targetDir </> relPath
357+
createDirectoryIfMissing True (toFilePath $ parent fullPath)
358+
B.writeFile (toFilePath fullPath) content
359+
360+
exitOnException
361+
:: Stream (Of a) (ExceptT e IO) r -> Stream (Of a) IO (Either e r)
362+
exitOnException strm = effect $ do
363+
x <- runExceptT $ S.next strm
364+
pure $ case x of
365+
Left e -> pure $ Left e
366+
Right (Left r) -> pure $ Right r
367+
Right (Right (a, rest)) -> do
368+
yield a >> exitOnException rest
369+
370+
copyGithubDirectory
371+
:: Auth
372+
-> Repository
373+
-> Maybe Commit
374+
-> Path Rel Dir
375+
-- ^ Source directory in the GitHub repository
376+
-> Path Abs Dir
377+
-- ^ Target directory on the local filesystem
378+
-> IO (Either GetGithubFileFailure ())
379+
copyGithubDirectory auth repo commitM srcDir targetDir =
380+
githubStreamDirectoryContents auth repo commitM srcDir
381+
& exitOnException
382+
& writeToDirectory targetDir

cli/src/User/Agent/PushTest.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ ENV TZ="UTC"
7070
RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && \
7171
echo ${TZ} > /etc/timezone
7272

73-
ADD docker-compose.yaml /docker-compose.yaml
74-
ADD testnet.yaml /testnet.yaml
73+
COPY . /
7574

7675
RUN sed -i 's/${INTERNAL_NETWORK}/false/g' /docker-compose.yaml
7776
|]

cli/src/Validation.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ data Validation m = Validation
104104
. String
105105
-> (FilePath -> m a)
106106
-> m a
107-
, writeTextFile :: FilePath -> Text -> m () -- Added writeFile to Validation
107+
, writeTextFile :: FilePath -> Text -> m ()
108108
, withCurrentDirectory :: forall a. FilePath -> m a -> m a
109-
, directoryExists :: Directory -> m (Maybe Permissions) -- Added directoryExists to Validation
109+
, directoryExists :: Directory -> m (Maybe Permissions)
110110
, decodePrivateSSHFile :: SSHClient 'WithSelector -> m (Maybe KeyPair)
111111
}
112112

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{-# LANGUAGE TemplateHaskell #-}
2+
3+
module Lib.GitHubSpec (spec) where
4+
5+
import Control.Monad (void)
6+
import Core.Types.Basic (Commit (..), Repository (..))
7+
import Data.ByteString.Char8 qualified as BC
8+
import GitHub (Auth (OAuth))
9+
import Lib.GitHub (copyGithubDirectory)
10+
import Path
11+
( Dir,
12+
Path,
13+
parseAbsDir,
14+
toFilePath,
15+
(</>),
16+
mkRelDir,
17+
mkRelFile,
18+
parseRelFile,
19+
File,
20+
Rel )
21+
import System.Directory (doesFileExist)
22+
import System.Environment (getEnv)
23+
import System.IO.Temp (withSystemTempDirectory)
24+
import Test.Hspec
25+
( Spec
26+
, beforeAll
27+
, describe
28+
, it
29+
, shouldReturn
30+
)
31+
32+
boot :: IO Auth
33+
boot = OAuth . BC.pack <$> getEnv "ANTI_GITHUB_PAT"
34+
35+
repo :: Repository
36+
repo =
37+
Repository
38+
{ organization = "cardano-foundation"
39+
, project = "hal-fixture-sin"
40+
}
41+
42+
commit :: Maybe Commit
43+
commit = Just $ Commit "a2572c4c9c37c3aa5f21e3ac8ce7ea9b96d833e5"
44+
45+
srcPath :: Path Rel Dir
46+
srcPath = $(mkRelDir "antithesis-test")
47+
48+
readmePath :: Path Rel File
49+
readmePath = $(mkRelFile "README.md")
50+
51+
dockerComposePath :: Path Rel File
52+
dockerComposePath = $(mkRelFile "docker-compose.yaml")
53+
54+
testnetPath :: Path Rel File
55+
testnetPath = $(mkRelFile "testnet.yaml")
56+
57+
spec :: Spec
58+
spec = do
59+
describe "Path lib" $ do
60+
it "parses relative directory" $ do
61+
void $ parseRelFile @IO "antithesis-test/somefile.txt"
62+
beforeAll boot $ do
63+
describe "Lib.GitHub" $ do
64+
it "downloads a directory" $ \pat ->
65+
withSystemTempDirectory "github-test" $ \targetPath -> do
66+
targetDir <- parseAbsDir targetPath
67+
copyGithubDirectory pat repo commit srcPath targetDir
68+
`shouldReturn` Right ()
69+
let readmeAbsPath = targetDir </> srcPath </> readmePath
70+
print $ "Checking for file: " ++ toFilePath readmeAbsPath
71+
doesFileExist (toFilePath readmeAbsPath) `shouldReturn` True
72+
let dockerComposeAbsPath =
73+
targetDir
74+
</> srcPath
75+
</> dockerComposePath
76+
doesFileExist (toFilePath dockerComposeAbsPath)
77+
`shouldReturn` True
78+
let testnetAbsPath = targetDir </> srcPath </> testnetPath
79+
doesFileExist (toFilePath testnetAbsPath) `shouldReturn` True

cli/test-integration/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}

0 commit comments

Comments
 (0)