Skip to content

Commit e75d284

Browse files
Add publish.credentials config key, use it to publish
1 parent 3b837e1 commit e75d284

File tree

11 files changed

+373
-86
lines changed

11 files changed

+373
-86
lines changed

modules/cli/src/main/scala/scala/cli/commands/config/Config.scala

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ import scala.cli.commands.ScalaCommand
1212
import scala.cli.commands.publish.ConfigUtil.*
1313
import scala.cli.commands.util.CommonOps.*
1414
import scala.cli.commands.util.JvmUtils
15-
import scala.cli.config.{ConfigDb, Keys, PasswordOption, RepositoryCredentials, Secret}
15+
import scala.cli.config.{
16+
ConfigDb,
17+
Keys,
18+
PasswordOption,
19+
PublishCredentials,
20+
RepositoryCredentials,
21+
Secret
22+
}
1623

1724
object Config extends ScalaCommand[ConfigOptions] {
1825
override def hidden = true
@@ -188,6 +195,32 @@ object Config extends ScalaCommand[ConfigOptions] {
188195
val newValue = credentials :: previousValueOpt.getOrElse(Nil)
189196
db.set(Keys.repositoryCredentials, newValue)
190197
}
198+
199+
case Keys.publishCredentials =>
200+
val (host, rawUser, rawPassword, realmOpt) = values match {
201+
case Seq(host, rawUser, rawPassword) => (host, rawUser, rawPassword, None)
202+
case Seq(host, rawUser, rawPassword, realm) =>
203+
(host, rawUser, rawPassword, Some(realm))
204+
case _ =>
205+
System.err.println(
206+
s"Usage: $progName config ${Keys.publishCredentials.fullName} host user password [realm]"
207+
)
208+
System.err.println(
209+
"Note that user and password are assumed to be secrets, specified like value:... or env:ENV_VAR_NAME, see https://scala-cli.virtuslab.org/docs/reference/password-options for more details"
210+
)
211+
sys.exit(1)
212+
}
213+
val (userOpt, passwordOpt) = (parseSecret(rawUser), parseSecret(rawPassword))
214+
.traverseN
215+
.left.map(CompositeBuildException(_))
216+
.orExit(logger)
217+
val credentials =
218+
PublishCredentials(host, userOpt, passwordOpt, realm = realmOpt)
219+
val previousValueOpt =
220+
db.get(Keys.publishCredentials).wrapConfigException.orExit(logger)
221+
val newValue = credentials :: previousValueOpt.getOrElse(Nil)
222+
db.set(Keys.publishCredentials, newValue)
223+
191224
case _ =>
192225
val finalValues =
193226
if (options.passwordValue && entry.isPasswordOption)

modules/cli/src/main/scala/scala/cli/commands/publish/OptionChecks.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ object OptionChecks {
2323
NameCheck(options, workspace, logger),
2424
ComputeVersionCheck(options, workspace, logger),
2525
RepositoryCheck(options, logger),
26-
UserCheck(options, () => configDb, logger),
27-
PasswordCheck(options, () => configDb, logger),
26+
UserCheck(options, () => configDb, workspace, logger),
27+
PasswordCheck(options, () => configDb, workspace, logger),
2828
PgpSecretKeyCheck(options, coursierCache, () => configDb, logger, backend),
2929
LicenseCheck(options, logger),
3030
UrlCheck(options, workspace, logger),

modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import scala.cli.commands.{
4848
SharedPythonOptions,
4949
WatchUtil
5050
}
51-
import scala.cli.config.{ConfigDb, Keys}
51+
import scala.cli.config.{ConfigDb, Keys, PublishCredentials}
5252
import scala.cli.errors.{
5353
FailedToSignFileError,
5454
MalformedChecksumsError,
@@ -688,29 +688,44 @@ object Publish extends ScalaCommand[PublishOptions] with BuildCommandHelpers {
688688
val ec = builds.head.options.finalCache.ec
689689

690690
def authOpt(repo: String): Either[BuildException, Option[Authentication]] = either {
691-
val hostOpt = {
691+
val isHttps = {
692692
val uri = new URI(repo)
693-
if (uri.getScheme == "https") Some(uri.getHost)
694-
else None
693+
uri.getScheme == "https"
694+
}
695+
val hostOpt = Option.when(isHttps)(new URI(repo).getHost)
696+
val maybeCredentials: Either[BuildException, Option[PublishCredentials]] = hostOpt match {
697+
case None => Right(None)
698+
case Some(host) =>
699+
configDb().get(Keys.publishCredentials).wrapConfigException.map { credListOpt =>
700+
credListOpt.flatMap { credList =>
701+
credList.find { cred =>
702+
cred.host == host &&
703+
(isHttps || cred.httpsOnly.contains(false))
704+
}
705+
}
706+
}
695707
}
696708
val isSonatype =
697709
hostOpt.exists(host => host == "oss.sonatype.org" || host.endsWith(".oss.sonatype.org"))
698710
val passwordOpt = publishOptions.contextual(isCi).repoPassword match {
699-
case None if isSonatype =>
700-
value(configDb().get(Keys.sonatypePassword).wrapConfigException)
711+
case None => value(maybeCredentials).flatMap(_.password)
701712
case other => other.map(_.toConfig)
702713
}
703714
passwordOpt.map(_.get()) match {
704715
case None => None
705716
case Some(password) =>
706717
val userOpt = publishOptions.contextual(isCi).repoUser match {
707-
case None if isSonatype =>
708-
value(configDb().get(Keys.sonatypeUser).wrapConfigException)
718+
case None => value(maybeCredentials).flatMap(_.user)
709719
case other => other.map(_.toConfig)
710720
}
711721
val realmOpt = publishOptions.contextual(isCi).repoRealm match {
712-
case None if isSonatype =>
713-
Some("Sonatype Nexus Repository Manager")
722+
case None =>
723+
value(maybeCredentials)
724+
.flatMap(_.realm)
725+
.orElse {
726+
if (isSonatype) Some("Sonatype Nexus Repository Manager")
727+
else None
728+
}
714729
case other => other
715730
}
716731
val auth = Authentication(userOpt.fold("")(_.get().value), password.value)
Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,88 @@
11
package scala.cli.commands.publish.checks
22

3+
import java.net.URI
4+
35
import scala.build.EitherCps.{either, value}
46
import scala.build.Logger
57
import scala.build.errors.BuildException
68
import scala.build.options.{PublishOptions => BPublishOptions}
79
import scala.cli.commands.publish.ConfigUtil._
8-
import scala.cli.commands.publish.{OptionCheck, PublishSetupOptions, SetSecret}
10+
import scala.cli.commands.publish.{OptionCheck, PublishSetupOptions, RepoParams, SetSecret}
911
import scala.cli.config.{ConfigDb, Keys}
1012
import scala.cli.errors.MissingPublishOptionError
1113

1214
final case class PasswordCheck(
1315
options: PublishSetupOptions,
1416
configDb: () => ConfigDb,
17+
workspace: os.Path,
1518
logger: Logger
1619
) extends OptionCheck {
1720
def kind = OptionCheck.Kind.Repository
1821
def fieldName = "password"
1922
def directivePath = "publish" + (if (options.publishParams.setupCi) ".ci" else "") + ".password"
2023

24+
private def hostOpt(pubOpt: BPublishOptions): Option[String] = {
25+
val repo = pubOpt.contextual(options.publishParams.setupCi).repository.getOrElse(
26+
RepositoryCheck.defaultRepository
27+
)
28+
RepoParams(
29+
repo,
30+
pubOpt.versionControl.map(_.url),
31+
workspace,
32+
None,
33+
false,
34+
null,
35+
logger
36+
) match {
37+
case Left(ex) =>
38+
logger.debug("Caught exception when trying to compute host to check user credentials")
39+
logger.debug(ex)
40+
None
41+
case Right(params) =>
42+
Some(new URI(params.repo.snapshotRepo.root).getHost)
43+
}
44+
}
45+
46+
private def passwordOpt(pubOpt: BPublishOptions) = hostOpt(pubOpt) match {
47+
case None => Right(None)
48+
case Some(host) =>
49+
configDb().get(Keys.publishCredentials).wrapConfigException.map { credListOpt =>
50+
credListOpt.flatMap { credList =>
51+
credList
52+
.iterator
53+
.filter(_.host == host)
54+
.map(_.password)
55+
.collectFirst {
56+
case Some(p) => p
57+
}
58+
}
59+
}
60+
}
61+
2162
def check(pubOpt: BPublishOptions): Boolean =
22-
!options.publishParams.setupCi ||
23-
pubOpt.retained(options.publishParams.setupCi).repoPassword.nonEmpty
63+
pubOpt.retained(options.publishParams.setupCi).repoPassword.nonEmpty || {
64+
!options.publishParams.setupCi && (passwordOpt(pubOpt) match {
65+
case Left(ex) =>
66+
logger.debug("Ignoring error while trying to get password from config")
67+
logger.debug(ex)
68+
true
69+
case Right(valueOpt) =>
70+
valueOpt.isDefined
71+
})
72+
}
2473

2574
def defaultValue(pubOpt: BPublishOptions): Either[BuildException, OptionCheck.DefaultValue] =
2675
either {
76+
2777
if (options.publishParams.setupCi) {
2878
val password = options.publishRepo.password match {
2979
case Some(password0) => password0.toConfig
3080
case None =>
31-
val passwordOpt = value(configDb().get(Keys.sonatypePassword).wrapConfigException)
32-
passwordOpt match {
81+
value(passwordOpt(pubOpt)) match {
3382
case Some(password0) =>
34-
logger.message("publish.password:")
83+
logger.message("publish.credentials:")
3584
logger.message(
36-
s" using ${Keys.sonatypePassword.fullName} from Scala CLI configuration"
85+
s" using ${Keys.publishCredentials.fullName} from Scala CLI configuration"
3786
)
3887
password0
3988
case None =>
@@ -42,8 +91,8 @@ final case class PasswordCheck(
4291
new MissingPublishOptionError(
4392
"publish password",
4493
"--password",
45-
"publish.password",
46-
configKeys = Seq(Keys.sonatypePassword.fullName)
94+
"publish.credentials",
95+
configKeys = Seq(Keys.publishCredentials.fullName)
4796
)
4897
}
4998
}
@@ -56,21 +105,30 @@ final case class PasswordCheck(
56105
Seq(SetSecret("PUBLISH_PASSWORD", password.get(), force = true))
57106
)
58107
}
59-
else if (value(configDb().get(Keys.sonatypePassword).wrapConfigException).isDefined) {
60-
logger.message("publish.password:")
61-
logger.message(s" found ${Keys.sonatypePassword.fullName} in Scala CLI configuration")
62-
OptionCheck.DefaultValue.empty
63-
}
64108
else
65-
value {
66-
Left {
67-
new MissingPublishOptionError(
68-
"publish password",
69-
"",
70-
"publish.password",
71-
configKeys = Seq(Keys.sonatypePassword.fullName)
72-
)
73-
}
109+
hostOpt(pubOpt) match {
110+
case None =>
111+
logger.debug("No host, not checking for publish repository password")
112+
OptionCheck.DefaultValue.empty
113+
case Some(host) =>
114+
if (value(passwordOpt(pubOpt)).isDefined) {
115+
logger.message("publish.password:")
116+
logger.message(
117+
s" found password for $host in ${Keys.publishCredentials.fullName} in Scala CLI configuration"
118+
)
119+
OptionCheck.DefaultValue.empty
120+
}
121+
else
122+
value {
123+
Left {
124+
new MissingPublishOptionError(
125+
"publish password",
126+
"",
127+
"publish.credentials",
128+
configKeys = Seq(Keys.publishCredentials.fullName)
129+
)
130+
}
131+
}
74132
}
75133
}
76134
}

0 commit comments

Comments
 (0)