Add refresh-auth (#64)

This commit is contained in:
Patrick Stevens
2023-08-07 16:35:46 +01:00
committed by GitHub
parent 079725b8ac
commit a8f9e6a9f3
9 changed files with 215 additions and 29 deletions

View File

@@ -149,6 +149,16 @@ module Gitea =
Error (Map.ofArray errors) Error (Map.ofArray errors)
} }
let private createPushMirrorOption (target : Uri) (githubToken : string) : Gitea.CreatePushMirrorOption =
let options = Gitea.CreatePushMirrorOption ()
options.SyncOnCommit <- Some true
options.RemoteAddress <- target.ToString ()
options.RemoteUsername <- githubToken
options.RemotePassword <- githubToken
options.Interval <- "8h0m0s"
options
let reconcileDifferingConfiguration let reconcileDifferingConfiguration
(logger : ILogger) (logger : ILogger)
(client : IGiteaClient) (client : IGiteaClient)
@@ -369,13 +379,8 @@ module Gitea =
| Some token -> | Some token ->
async { async {
logger.LogInformation ("Setting up push mirror on {User}:{Repo}", user, repoName) logger.LogInformation ("Setting up push mirror on {User}:{Repo}", user, repoName)
let options = Gitea.CreatePushMirrorOption () let pushMirrorOption = createPushMirrorOption desired.GitHubAddress token
options.SyncOnCommit <- Some true let! _ = client.RepoAddPushMirror (user, repoName, pushMirrorOption) |> Async.AwaitTask
options.RemoteAddress <- (desired.GitHubAddress : Uri).ToString ()
options.RemoteUsername <- token
options.RemotePassword <- token
options.Interval <- "8h0m0s"
let! _ = client.RepoAddPushMirror (user, repoName, options) |> Async.AwaitTask
return () return ()
} }
| Some desired, Some actual -> | Some desired, Some actual ->
@@ -721,3 +726,88 @@ module Gitea =
) )
|> Async.Parallel |> Async.Parallel
|> fun a -> async.Bind (a, Array.iter id >> async.Return) |> fun a -> async.Bind (a, Array.iter id >> async.Return)
let toRefresh (client : IGiteaClient) : Async<Map<User, Map<RepoName, Gitea.PushMirror list>>> =
async {
let! users =
Array.getPaginated (fun page limit ->
client.AdminGetAllUsers (Some page, Some limit) |> Async.AwaitTask
)
let! results =
users
|> Seq.map (fun user ->
async {
let! repos =
Array.getPaginated (fun page count ->
client.UserListRepos (user.LoginName, Some page, Some count) |> Async.AwaitTask
)
let! pushMirrorResults =
repos
|> Seq.map (fun r ->
async {
let! mirrors =
Array.getPaginated (fun page count ->
Async.AwaitTask (
client.RepoListPushMirrors (
user.LoginName,
r.Name,
Some page,
Some count
)
)
)
return RepoName r.Name, mirrors
}
)
|> Async.Parallel
return User user.LoginName, Map.ofArray pushMirrorResults
}
)
|> Async.Parallel
return results |> Map.ofArray
}
let refreshAuth
(logger : ILogger)
(client : IGiteaClient)
(githubToken : string)
(instructions : Map<User, Map<RepoName, Gitea.PushMirror list>>)
: Async<unit>
=
instructions
|> Map.toSeq
|> Seq.collect (fun (User user, repos) ->
Map.toSeq repos
|> Seq.collect (fun (RepoName repoName, mirrors) ->
mirrors
|> Seq.map (fun mirror ->
async {
logger.LogInformation (
"Refreshing push mirror on {User}:{Repo} to {PushMirrorRemote}",
user,
repoName,
mirror.RemoteAddress
)
let option = createPushMirrorOption (Uri mirror.RemoteAddress) githubToken
option.Interval <- mirror.Interval
option.SyncOnCommit <- mirror.SyncOnCommit
let! newMirror = Async.AwaitTask (client.RepoAddPushMirror (user, repoName, option))
let! deleteOldMirror =
Async.AwaitTask (client.RepoDeletePushMirror (user, repoName, mirror.RemoteName))
return ()
}
)
)
)
|> Async.Parallel
|> Async.map (Array.iter id)

View File

@@ -17,6 +17,8 @@ type IGiteaClient =
loginName : string * userName : string * page : int64 option * count : int64 option -> loginName : string * userName : string * page : int64 option * count : int64 option ->
Gitea.PushMirror array Task Gitea.PushMirror array Task
abstract RepoDeletePushMirror : user : string * repo : string * remoteName : string -> unit Task
abstract RepoListBranchProtection : loginName : string * userName : string -> Gitea.BranchProtection array Task abstract RepoListBranchProtection : loginName : string * userName : string -> Gitea.BranchProtection array Task
abstract RepoDeleteBranchProtection : user : string * repo : string * branch : string -> unit Task abstract RepoDeleteBranchProtection : user : string * repo : string * branch : string -> unit Task
@@ -57,6 +59,9 @@ module IGiteaClient =
member _.RepoListPushMirrors (loginName, userName, page, count) = member _.RepoListPushMirrors (loginName, userName, page, count) =
client.RepoListPushMirrors (loginName, userName, page, count) client.RepoListPushMirrors (loginName, userName, page, count)
member _.RepoDeletePushMirror (loginName, repo, remoteName) =
client.RepoDeletePushMirror (loginName, repo, remoteName)
member _.RepoListBranchProtection (login, user) = member _.RepoListBranchProtection (login, user) =
client.RepoListBranchProtection (login, user) client.RepoListBranchProtection (login, user)

View File

@@ -18,8 +18,10 @@
<ItemGroup> <ItemGroup>
<Compile Include="ArgsCrate.fs" /> <Compile Include="ArgsCrate.fs" />
<Compile Include="Result.fs" /> <Compile Include="Result.fs" />
<Compile Include="Utils.fs" />
<Compile Include="Reconcile.fs" /> <Compile Include="Reconcile.fs" />
<Compile Include="OutputSchema.fs" /> <Compile Include="OutputSchema.fs" />
<Compile Include="RefreshAuth.fs" />
<Compile Include="Verify.fs" /> <Compile Include="Verify.fs" />
<Compile Include="Program.fs" /> <Compile Include="Program.fs" />
<None Include="..\README.md" Pack="true" PackagePath="/" /> <None Include="..\README.md" Pack="true" PackagePath="/" />

View File

@@ -22,6 +22,9 @@ module Program =
ArgsCrate.make OutputSchemaArgs.OfParse OutputSchema.run) ArgsCrate.make OutputSchemaArgs.OfParse OutputSchema.run)
"verify", ("Verify a `reconcile` configuration file", ArgsCrate.make VerifyArgs.OfParse Verify.run) "verify", ("Verify a `reconcile` configuration file", ArgsCrate.make VerifyArgs.OfParse Verify.run)
"refresh-auth",
("Refresh authentication for push mirrors", ArgsCrate.make RefreshAuthArgs.OfParse RefreshAuth.run)
|] |]
|> Map.ofArray |> Map.ofArray

View File

@@ -2,10 +2,7 @@ namespace Gitea.Declarative
open System open System
open System.IO open System.IO
open System.Net.Http
open Argu open Argu
open Microsoft.Extensions.Logging.Console
open Microsoft.Extensions.Options
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
type RunArgsFragment = type RunArgsFragment =
@@ -61,28 +58,12 @@ module Reconcile =
let config = GiteaConfig.get args.ConfigFile let config = GiteaConfig.get args.ConfigFile
let options =
let options = ConsoleLoggerOptions ()
{ new IOptionsMonitor<ConsoleLoggerOptions> with
member _.Get _ = options
member _.CurrentValue = options
member _.OnChange _ =
{ new IDisposable with
member _.Dispose () = ()
}
}
async { async {
use loggerProvider = new ConsoleLoggerProvider (options) use loggerProvider = Utils.createLoggerProvider ()
let logger = loggerProvider.CreateLogger "Gitea.Declarative" let logger = loggerProvider.CreateLogger "Gitea.Declarative"
use client = new HttpClient () use httpClient = Utils.createHttpClient args.GiteaHost args.GiteaAdminApiToken
client.BaseAddress <- args.GiteaHost let client = Gitea.Client httpClient |> IGiteaClient.fromReal
client.DefaultRequestHeaders.Add ("Authorization", $"token {args.GiteaAdminApiToken}")
let client = Gitea.Client client |> IGiteaClient.fromReal
logger.LogInformation "Checking users..." logger.LogInformation "Checking users..."
let! userErrors = Gitea.checkUsers config client let! userErrors = Gitea.checkUsers config client

View File

@@ -0,0 +1,66 @@
namespace Gitea.Declarative
open System
open Argu
open Microsoft.Extensions.Logging
type RefreshAuthArgsFragment =
| [<ExactlyOnce ; EqualsAssignmentOrSpaced>] Gitea_Host of string
| [<ExactlyOnce ; EqualsAssignmentOrSpaced ; CustomAppSettings "GITEA_ADMIN_API_TOKEN">] Gitea_Admin_Api_Token of
string
| [<Unique ; EqualsAssignmentOrSpaced ; CustomAppSettings "GITHUB_API_TOKEN">] GitHub_Api_Token of string
| Dry_Run
interface IArgParserTemplate with
member s.Usage =
match s with
| Gitea_Host _ -> "the Gitea host, e.g. https://gitea.mydomain.com"
| Gitea_Admin_Api_Token _ ->
"a Gitea admin user's API token; can be read from the environment variable GITEA_ADMIN_API_TOKEN"
| GitHub_Api_Token _ ->
"a GitHub API token with read access to every desired sync-from-GitHub repo; can be read from the environment variable GITHUB_API_TOKEN"
| Dry_Run -> "don't actually update the mirrors"
type RefreshAuthArgs =
{
GiteaHost : Uri
GiteaAdminApiToken : string
GitHubApiToken : string
DryRun : bool
}
static member OfParse
(parsed : ParseResults<RefreshAuthArgsFragment>)
: Result<RefreshAuthArgs, ArguParseException>
=
try
{
GiteaHost = parsed.GetResult RefreshAuthArgsFragment.Gitea_Host |> Uri
GiteaAdminApiToken = parsed.GetResult RefreshAuthArgsFragment.Gitea_Admin_Api_Token
GitHubApiToken = parsed.GetResult RefreshAuthArgsFragment.GitHub_Api_Token
DryRun = parsed.TryGetResult RefreshAuthArgsFragment.Dry_Run |> Option.isSome
}
|> Ok
with :? ArguParseException as e ->
Error e
[<RequireQualifiedAccess>]
module RefreshAuth =
let run (args : RefreshAuthArgs) : Async<int> =
async {
use httpClient = Utils.createHttpClient args.GiteaHost args.GiteaAdminApiToken
let client = Gitea.Client httpClient |> IGiteaClient.fromReal
use loggerProvider = Utils.createLoggerProvider ()
let logger = loggerProvider.CreateLogger "Gitea.Declarative"
let! instructions = Gitea.toRefresh client
if args.DryRun then
logger.LogInformation ("Stopping due to --dry-run.")
else
do! Gitea.refreshAuth logger client args.GitHubApiToken instructions
return 0
}

View File

@@ -0,0 +1,32 @@
namespace Gitea.Declarative
open System
open System.Net.Http
open Microsoft.Extensions.Logging.Console
open Microsoft.Extensions.Options
[<RequireQualifiedAccess>]
module internal Utils =
let createLoggerProvider () =
let options =
let options = ConsoleLoggerOptions ()
{ new IOptionsMonitor<ConsoleLoggerOptions> with
member _.Get _ = options
member _.CurrentValue = options
member _.OnChange _ =
{ new IDisposable with
member _.Dispose () = ()
}
}
new ConsoleLoggerProvider (options)
let createHttpClient (host : Uri) (apiKey : string) =
let client = new HttpClient ()
client.BaseAddress <- host
client.DefaultRequestHeaders.Add ("Authorization", $"token {apiKey}")
client

View File

@@ -75,6 +75,8 @@ module Client =
member _.RepoAddPushMirror (user, repo, createPushMirrorOption) = failwith "Not implemented" member _.RepoAddPushMirror (user, repo, createPushMirrorOption) = failwith "Not implemented"
member _.RepoDeletePushMirror (user, repo, remoteName) = failwith "Not implemented"
member _.RepoListPushMirrors (loginName, userName, page, count) = failwith "Not implemented" member _.RepoListPushMirrors (loginName, userName, page, count) = failwith "Not implemented"
member _.RepoListBranchProtection (loginName, userName) = failwith "Not implemented" member _.RepoListBranchProtection (loginName, userName) = failwith "Not implemented"

View File

@@ -35,6 +35,7 @@ type GiteaClientMock =
UserListRepos : string * int64 option * int64 option -> Gitea.Repository array Task UserListRepos : string * int64 option * int64 option -> Gitea.Repository array Task
RepoAddPushMirror : string * string * Gitea.CreatePushMirrorOption -> Gitea.PushMirror Task RepoAddPushMirror : string * string * Gitea.CreatePushMirrorOption -> Gitea.PushMirror Task
RepoDeletePushMirror : string * string * string -> unit Task
RepoListPushMirrors : string * string * int64 option * int64 option -> Gitea.PushMirror array Task RepoListPushMirrors : string * string * int64 option * int64 option -> Gitea.PushMirror array Task
RepoListBranchProtection : string * string -> Gitea.BranchProtection array Task RepoListBranchProtection : string * string -> Gitea.BranchProtection array Task
@@ -64,6 +65,7 @@ type GiteaClientMock =
UserListRepos = fun _ -> failwith "Unimplemented" UserListRepos = fun _ -> failwith "Unimplemented"
RepoAddPushMirror = fun _ -> failwith "Unimplemented" RepoAddPushMirror = fun _ -> failwith "Unimplemented"
RepoDeletePushMirror = fun _ -> failwith "Unimplemented"
RepoListPushMirrors = fun _ -> failwith "Unimplemented" RepoListPushMirrors = fun _ -> failwith "Unimplemented"
RepoListBranchProtection = fun _ -> failwith "Unimplemented" RepoListBranchProtection = fun _ -> failwith "Unimplemented"
@@ -96,6 +98,9 @@ type GiteaClientMock =
member this.RepoListPushMirrors (loginName, userName, page, count) = member this.RepoListPushMirrors (loginName, userName, page, count) =
this.RepoListPushMirrors (loginName, userName, page, count) this.RepoListPushMirrors (loginName, userName, page, count)
member this.RepoDeletePushMirror (loginName, userName, remoteName) =
this.RepoDeletePushMirror (loginName, userName, remoteName)
member this.RepoListBranchProtection (login, user) = member this.RepoListBranchProtection (login, user) =
this.RepoListBranchProtection (login, user) this.RepoListBranchProtection (login, user)