@@ -22,18 +22,17 @@ import java.nio.file.{Path, Paths}
2222import java .security .SecureRandom
2323import java .security .cert .{CertificateFactory , X509Certificate }
2424import java .util .concurrent .{ExecutorService , Executors , ScheduledExecutorService , ThreadFactory }
25-
2625import journal .Logger
2726import nelson .BannedClientsConfig .HttpUserAgent
2827import nelson .cleanup .ExpirationPolicy
28+ import nelson .Github .GithubOp
2929import org .http4s .Uri
3030import org .http4s .client .Client
3131import org .http4s .client .blaze ._
3232import storage .StoreOp
33- import logging .{WorkflowLogger ,LoggingOp }
34- import audit .{Auditor ,AuditEvent }
35- import notifications .{SlackHttp ,SlackOp ,EmailOp ,EmailServer }
36-
33+ import logging .{LoggingOp , WorkflowLogger }
34+ import audit .{AuditEvent , Auditor }
35+ import notifications .{EmailOp , EmailServer , SlackHttp , SlackOp }
3736import scala .concurrent .ExecutionContext
3837import scala .concurrent .duration ._
3938import scala .util .control .NonFatal
@@ -45,57 +44,139 @@ import scheduler.SchedulerOp
4544import vault ._
4645import vault .http4s ._
4746
48- /**
49- *
50- */
51- final case class GithubConfig (
52- domain : Option [String ],
53- clientId : String ,
54- clientSecret : String ,
55- redirectUri : String ,
56- scope : String ,
57- systemAccessToken : AccessToken ,
58- systemUsername : String ,
59- organizationBlacklist : List [String ],
60- organizationAdminList : List [String ]
61- ){
47+ sealed abstract class ScmConfig extends Product with Serializable {
48+ def domain : Option [String ]
49+ def clientId : String
50+ def clientSecret : String
51+ def redirectUri : String
52+ def scope : String
53+ def systemAccessToken : AccessToken
54+ def systemUsername : String
55+ def organizationBlacklist : List [String ]
56+ def organizationAdminList : List [String ]
6257 def isEnterprise : Boolean = domain.nonEmpty
58+ def base : String
59+ def oauth : String
60+ def api : String
61+ def tokenEndpoint : String
62+ def loginEndpoint : String
63+ def userEndpoint : String
64+ def userOrgsEndpoint : String
65+ def orgEndpoint (login : String ): String
66+ def repoEndpoint (page : Int = 1 ): String
67+ def webhookEndpoint (slug : Slug ): String
68+ def contentsEndpoint (slug : Slug , path : String ): String
69+ def releaseEndpoint (slug : Slug , releaseId : String ): String
70+
71+ def withOrganizationAdminList (l : List [String ]): ScmConfig
72+
73+ private [nelson] def encodeURI (uri : String ): String =
74+ java.net.URLEncoder .encode(uri, " UTF-8" )
75+ }
76+ object ScmConfig {
77+ final case class GithubConfig (
78+ domain : Option [String ],
79+ clientId : String ,
80+ clientSecret : String ,
81+ redirectUri : String ,
82+ scope : String ,
83+ systemAccessToken : AccessToken ,
84+ systemUsername : String ,
85+ organizationBlacklist : List [String ],
86+ organizationAdminList : List [String ]) extends ScmConfig {
6387
64- val oauth =
65- " https://" + domain.fold(" github.com" )(identity)
88+ val base : String =
89+ " https://" + domain.fold(" github.com" )(identity)
6690
67- val api =
68- " https:// " + domain.fold( " api.github.com " )( _+ " /api/v3 " )
91+ val oauth : String =
92+ base
6993
70- val tokenEndpoint =
71- s " ${oauth} /login/oauth/access_token "
94+ val api : String =
95+ " https:// " + domain.fold( " api.github.com " )( _+ " /api/v3 " )
7296
73- val loginEndpoint =
74- s " ${ oauth} /login/oauth/authorize?client_id= ${clientId} &redirect_uri= ${encodeURI(redirectUri)} &scope= ${scope} "
97+ val tokenEndpoint : String =
98+ s " $oauth/login/oauth/access_token "
7599
76- val userEndpoint =
77- s " ${api} /user "
100+ val loginEndpoint : String =
101+ s " $oauth /login/oauth/authorize?client_id= $clientId &redirect_uri= ${encodeURI(redirectUri)} &scope= $scope "
78102
79- val userOrgsEndpoint =
80- s " ${userEndpoint} /orgs "
103+ val userEndpoint : String =
104+ s " $api /user "
81105
82- def orgEndpoint ( login : String ) =
83- s " ${api} /orgs/ ${login} "
106+ val userOrgsEndpoint : String =
107+ s " $userEndpoint /orgs "
84108
85- def repoEndpoint ( page : Int = 1 ) =
86- s " ${ api} /user/repos?affiliation=owner,organization_member&visibility=all&direction=asc&page= ${page} "
109+ def orgEndpoint ( login : String ) : String =
110+ s " $api/orgs/ $login "
87111
88- def webhookEndpoint ( slug : Slug ) =
89- s " ${ api} /repos/ ${slug} /hooks "
112+ def repoEndpoint ( page : Int = 1 ) : String =
113+ s " $api/user/repos?affiliation=owner,organization_member&visibility=all&direction=asc&page= $page "
90114
91- def contentsEndpoint (slug : Slug , path : String ) =
92- s " ${ api} /repos/ ${ slug} /contents/ ${path} "
115+ def webhookEndpoint (slug : Slug ) : String =
116+ s " $api/repos/ $slug/hooks "
93117
94- def releaseEndpoint (slug : Slug , releaseId : Long ) =
95- s " ${ api} /repos/ ${ slug} /releases/ ${releaseId} "
118+ def contentsEndpoint (slug : Slug , path : String ) : String =
119+ s " $api/repos/ $slug/contents/ $path "
96120
97- private [nelson] def encodeURI (uri : String ): String =
98- java.net.URLEncoder .encode(uri, " UTF-8" )
121+ def releaseEndpoint (slug : Slug , releaseId : String ): String =
122+ s " $api/repos/ $slug/releases/ $releaseId"
123+
124+ def withOrganizationAdminList (l : List [String ]): ScmConfig =
125+ this .copy(organizationAdminList = l)
126+ }
127+
128+ final case class GitlabConfig (
129+ domain : Option [String ],
130+ clientId : String ,
131+ clientSecret : String ,
132+ redirectUri : String ,
133+ scope : String ,
134+ systemAccessToken : AccessToken ,
135+ systemUsername : String ,
136+ organizationBlacklist : List [String ],
137+ organizationAdminList : List [String ]) extends ScmConfig {
138+
139+ val base : String =
140+ " https://" + domain.fold(" gitlab.com" )(identity)
141+
142+ val oauth : String =
143+ base + " /oauth"
144+
145+ val api : String =
146+ base + " /api/v4"
147+
148+ val tokenEndpoint : String =
149+ s " $oauth/token "
150+
151+ val loginEndpoint : String =
152+ s " $oauth/authorize?client_id= $clientId&redirect_uri= ${encodeURI(redirectUri)}&response_type=code "
153+
154+ val userEndpoint : String =
155+ s " $api/user "
156+
157+ val userOrgsEndpoint : String =
158+ s " $api/groups "
159+
160+ def orgEndpoint (login : String ): String =
161+ s " $api/groups/ $login"
162+
163+ def repoEndpoint (page : Int = 1 ): String =
164+ s " $api/projects?page= $page"
165+
166+ def webhookEndpoint (slug : Slug ): String =
167+ s " $api/projects/ ${ urlEncode(slug.toString) }/hooks "
168+
169+ def contentsEndpoint (slug : Slug , path : String ): String =
170+ s " $api/projects/ ${ urlEncode(slug.toString) }/repository/files/ $path"
171+
172+ def releaseEndpoint (slug : Slug , releaseId : String ): String =
173+ s " $api/projects/ ${ urlEncode(slug.toString) }/repository/tags/ $releaseId"
174+
175+ def withOrganizationAdminList (l : List [String ]): ScmConfig =
176+ this .copy(organizationAdminList = l)
177+
178+ private [this ] def urlEncode (s : String ) = java.net.URLEncoder .encode(s, " UTF-8" )
179+ }
99180}
100181
101182/**
@@ -316,7 +397,7 @@ final case class PolicyConfig(
316397 * actually cares about.
317398 */
318399final case class NelsonConfig (
319- git : GithubConfig ,
400+ git : ScmConfig ,
320401 network : NetworkConfig ,
321402 security : SecurityConfig ,
322403 database : DatabaseConfig ,
@@ -400,7 +481,12 @@ object Config {
400481 val nomadcfg = readNomad(cfg.subconfig(" nelson.nomad" ))
401482
402483 val gitcfg = readGithub(cfg.subconfig(" nelson.github" ))
403- val git = new Github .GithubHttp (gitcfg, http0)
484+ val git : GithubOp ~> Task = gitcfg match {
485+ case _ : ScmConfig .GithubConfig =>
486+ new Github .GithubHttp (gitcfg, http0)
487+ case _ =>
488+ new Gitlab .GitlabHttp (gitcfg, http4sClient(timeout))
489+ }
404490
405491 val workflowConf = readWorkflowLogger(cfg.subconfig(" nelson.workflow-logger" ))
406492 val workflowlogger = new WorkflowLogger (
@@ -754,18 +840,24 @@ object Config {
754840 monitoringPort = cfg.require[Int ](" monitoring-port" )
755841 )
756842
757- private def readGithub (cfg : KConfig ): GithubConfig =
758- GithubConfig (
759- domain = cfg.lookup[String ](" domain" ),
760- clientId = cfg.require[String ](" client-id" ),
761- clientSecret = cfg.require[String ](" client-secret" ),
762- redirectUri = cfg.require[String ](" redirect-uri" ),
763- scope = cfg.require[String ](" scope" ),
764- systemAccessToken = AccessToken (cfg.require[String ](" access-token" )),
765- systemUsername = cfg.require[String ](" system-username" ),
766- organizationBlacklist = cfg.lookup[List [String ]](" organization-blacklist" ).getOrElse(Nil ),
767- organizationAdminList = cfg.lookup[List [String ]](" organization-admins" ).getOrElse(Nil )
843+ private def readGithub (cfg : KConfig ): ScmConfig = {
844+ val service = cfg.lookup[String ](" service" ).getOrElse(" github" )
845+ val attrs = (
846+ cfg.lookup[String ](" domain" ),
847+ cfg.require[String ](" client-id" ),
848+ cfg.require[String ](" client-secret" ),
849+ cfg.require[String ](" redirect-uri" ),
850+ cfg.require[String ](" scope" ),
851+ AccessToken (cfg.require[String ](" access-token" ), isPrivate = true ),
852+ cfg.require[String ](" system-username" ),
853+ cfg.lookup[List [String ]](" organization-blacklist" ).getOrElse(Nil ),
854+ cfg.lookup[List [String ]](" organization-admins" ).getOrElse(Nil )
768855 )
856+ import ScmConfig ._
857+ val confBuilder =
858+ if (service == " github" ) GithubConfig else GitlabConfig
859+ confBuilder.tupled(attrs)
860+ }
769861
770862 private def readSlack (cfg : KConfig ): Option [SlackConfig ] = {
771863 for {
0 commit comments