@@ -33,6 +33,7 @@ import qualified Data.Text.Lazy as TL
33
33
import qualified Data.Text.Lazy.Encoding as TLE
34
34
import Data.Time.Calendar ( toGregorian )
35
35
import Data.Time.Clock ( getCurrentTime , utctDay )
36
+ import Network.HTTP.Client ( applyBasicAuth )
36
37
import Network.HTTP.StackClient
37
38
( HttpException (.. ), HttpExceptionContent (.. )
38
39
, Response (.. ), VerifiedDownloadException (.. )
@@ -45,7 +46,10 @@ import Path ( (</>), dirname, parent, parseRelDir, parseRelFile )
45
46
import Path.IO
46
47
( doesDirExist , doesFileExist , ensureDir , getCurrentDir )
47
48
import RIO.Process ( proc , runProcess_ , withWorkingDir )
48
- import Stack.Constants ( backupUrlRelPath , wiredInPackages )
49
+ import Stack.Constants
50
+ ( altGitHubTokenEnvVar , backupUrlRelPath , gitHubBasicAuthType
51
+ , gitHubTokenEnvVar , wiredInPackages
52
+ )
49
53
import Stack.Constants.Config ( templatesDir )
50
54
import Stack.Prelude
51
55
import Stack.Types.Config ( Config (.. ), HasConfig (.. ), SCM (.. ) )
@@ -54,6 +58,7 @@ import Stack.Types.TemplateName
54
58
, TemplatePath (.. ), defaultTemplateName
55
59
, parseRepoPathWithService , templateName , templatePath
56
60
)
61
+ import System.Environment ( lookupEnv )
57
62
import qualified Text.Mustache as Mustache
58
63
import qualified Text.Mustache.Render as Mustache
59
64
import Text.ProjectTemplate
@@ -332,16 +337,15 @@ loadTemplate name logIt = do
332
337
(do f <- loadLocalFile relFile eitherByteStringToText
333
338
logIt LocalTemp
334
339
pure f)
335
- (\ (e :: PrettyException ) -> do
336
- case relSettings rawParam of
337
- Just settings -> do
338
- let url = tplDownloadUrl settings
339
- extract = tplExtract settings
340
- downloadTemplate url extract (templateDir </> relFile)
341
- Nothing -> throwM e
340
+ ( \ (e :: PrettyException ) -> do
341
+ settings <- fromMaybe (throwM e) (relSettings rawParam)
342
+ let url = tplDownloadUrl settings
343
+ mBasicAuth = tplBasicAuth settings
344
+ extract = tplExtract settings
345
+ downloadTemplate url mBasicAuth extract (templateDir </> relFile)
342
346
)
343
347
RepoPath rtp -> do
344
- let settings = settingsFromRepoTemplatePath rtp
348
+ settings <- settingsFromRepoTemplatePath rtp
345
349
downloadFromUrl settings templateDir
346
350
347
351
where
@@ -365,26 +369,30 @@ loadTemplate name logIt = do
365
369
else throwM $ PrettyException $
366
370
LoadTemplateFailed name path
367
371
368
- relSettings :: String -> Maybe TemplateDownloadSettings
372
+ relSettings :: String -> Maybe ( RIO env TemplateDownloadSettings )
369
373
relSettings req = do
370
374
rtp <- parseRepoPathWithService defaultRepoService (T. pack req)
371
375
pure (settingsFromRepoTemplatePath rtp)
372
376
373
377
downloadFromUrl :: TemplateDownloadSettings -> Path Abs Dir -> RIO env Text
374
378
downloadFromUrl settings templateDir = do
375
379
let url = tplDownloadUrl settings
380
+ mBasicAuth = tplBasicAuth settings
376
381
rel = fromMaybe backupUrlRelPath (parseRelFile url)
377
- downloadTemplate url (tplExtract settings) (templateDir </> rel)
378
-
379
- downloadTemplate :: String
380
- -> (ByteString
381
- -> Either String Text )
382
- -> Path Abs File
383
- -> RIO env Text
384
- downloadTemplate url extract path = do
382
+ downloadTemplate url mBasicAuth (tplExtract settings) (templateDir </> rel)
383
+
384
+ downloadTemplate ::
385
+ String
386
+ -> Maybe (ByteString , ByteString )
387
+ -- ^ Optional HTTP \'Basic\' authentication (type, credentials)
388
+ -> (ByteString -> Either String Text )
389
+ -> Path Abs File
390
+ -> RIO env Text
391
+ downloadTemplate url mBasicAuth extract path = do
385
392
req <- parseRequest url
386
- let dReq = setForceDownload True $
387
- mkDownloadRequest (setRequestCheckStatus req)
393
+ let authReq = maybe id (uncurry applyBasicAuth) mBasicAuth req
394
+ dReq = setForceDownload True $
395
+ mkDownloadRequest (setRequestCheckStatus authReq)
388
396
logIt RemoteTemp
389
397
catch
390
398
( do let label = T. pack $ toFilePath path
@@ -414,8 +422,11 @@ loadTemplate name logIt = do
414
422
throwM $ PrettyException $
415
423
DownloadTemplateFailed (templateName name) url exception
416
424
425
+ -- | Type representing settings for the download of Stack project templates.
417
426
data TemplateDownloadSettings = TemplateDownloadSettings
418
427
{ tplDownloadUrl :: String
428
+ , tplBasicAuth :: Maybe (ByteString , ByteString )
429
+ -- ^ Optional HTTP 'Basic' authentication (type, credentials)
419
430
, tplExtract :: ByteString -> Either String Text
420
431
}
421
432
@@ -425,40 +436,64 @@ eitherByteStringToText = mapLeft show . decodeUtf8'
425
436
asIsFromUrl :: String -> TemplateDownloadSettings
426
437
asIsFromUrl url = TemplateDownloadSettings
427
438
{ tplDownloadUrl = url
439
+ , tplBasicAuth = Nothing
428
440
, tplExtract = eitherByteStringToText
429
441
}
430
442
431
- -- | Construct a URL for downloading from a repo.
432
- settingsFromRepoTemplatePath :: RepoTemplatePath -> TemplateDownloadSettings
433
- settingsFromRepoTemplatePath (RepoTemplatePath GitHub user name) =
434
- -- T.concat ["https://raw.githubusercontent.com", "/", user, "/stack-templates/master/", name]
435
- TemplateDownloadSettings
436
- { tplDownloadUrl = concat
437
- [ " https://api.github.com/repos/"
438
- , T. unpack user
439
- , " /stack-templates/contents/"
440
- , T. unpack name
441
- ]
442
- , tplExtract = \ bs -> do
443
- decodedJson <- eitherDecode (LB. fromStrict bs)
444
- case decodedJson of
445
- Object o | Just (String content) <- KeyMap. lookup " content" o -> do
446
- let noNewlines = T. filter (/= ' \n ' )
447
- bsContent <- B64. decode $ T. encodeUtf8 (noNewlines content)
448
- mapLeft show $ decodeUtf8' bsContent
449
- _ ->
450
- Left " Couldn't parse GitHub response as a JSON object with a \" content\" field"
451
- }
452
-
453
- settingsFromRepoTemplatePath (RepoTemplatePath GitLab user name) =
443
+ -- | Construct settings for downloading a Stack project template from a
444
+ -- repository.
445
+ settingsFromRepoTemplatePath ::
446
+ HasTerm env
447
+ => RepoTemplatePath
448
+ -> RIO env TemplateDownloadSettings
449
+ settingsFromRepoTemplatePath (RepoTemplatePath GitHub user name) = do
450
+ let basicAuthMsg token = prettyInfoL
451
+ [ flow " Using content of"
452
+ , fromString token
453
+ , flow " environment variable to authenticate GitHub REST API."
454
+ ]
455
+ mBasicAuth <- do
456
+ wantGitHubToken <- liftIO $ fromMaybe " " <$> lookupEnv gitHubTokenEnvVar
457
+ if not (L. null wantGitHubToken)
458
+ then do
459
+ basicAuthMsg gitHubTokenEnvVar
460
+ pure $ Just (gitHubBasicAuthType, fromString wantGitHubToken)
461
+ else do
462
+ wantAltGitHubToken <-
463
+ liftIO $ fromMaybe " " <$> lookupEnv altGitHubTokenEnvVar
464
+ if not (L. null wantAltGitHubToken)
465
+ then do
466
+ basicAuthMsg altGitHubTokenEnvVar
467
+ pure $ Just (gitHubBasicAuthType, fromString wantAltGitHubToken)
468
+ else pure Nothing
469
+ pure $ TemplateDownloadSettings
470
+ { tplDownloadUrl = concat
471
+ [ " https://api.github.com/repos/"
472
+ , T. unpack user
473
+ , " /stack-templates/contents/"
474
+ , T. unpack name
475
+ ]
476
+ , tplBasicAuth = mBasicAuth
477
+ , tplExtract = \ bs -> do
478
+ decodedJson <- eitherDecode (LB. fromStrict bs)
479
+ case decodedJson of
480
+ Object o | Just (String content) <- KeyMap. lookup " content" o -> do
481
+ let noNewlines = T. filter (/= ' \n ' )
482
+ bsContent <- B64. decode $ T. encodeUtf8 (noNewlines content)
483
+ mapLeft show $ decodeUtf8' bsContent
484
+ _ ->
485
+ Left " Couldn't parse GitHub response as a JSON object with a \
486
+ \\" content\" field"
487
+ }
488
+ settingsFromRepoTemplatePath (RepoTemplatePath GitLab user name) = pure $
454
489
asIsFromUrl $ concat
455
490
[ " https://gitlab.com"
456
491
, " /"
457
492
, T. unpack user
458
493
, " /stack-templates/raw/master/"
459
494
, T. unpack name
460
495
]
461
- settingsFromRepoTemplatePath (RepoTemplatePath Bitbucket user name) =
496
+ settingsFromRepoTemplatePath (RepoTemplatePath Bitbucket user name) = pure $
462
497
asIsFromUrl $ concat
463
498
[ " https://bitbucket.org"
464
499
, " /"
0 commit comments