Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#4][#6] Close and release tasks #9

Merged
merged 20 commits into from
Jan 23, 2020
Merged

[#4][#6] Close and release tasks #9

merged 20 commits into from
Jan 23, 2020

Conversation

szpak
Copy link
Contributor

@szpak szpak commented Oct 18, 2019

The initial implementation of close and release tasks. It's effectively broken with the real Nexus instance (due to missing retrier to handle "in transition" periods - #7), but I wanted to cover it in another PR as I had some issue with Kotlin and I'm not sure about some Gradle related bindings here. Therefore, I would like to get faster feedback for the following code.

In the next PR I plan to work on:

  • retrier to cover "inTransition == true" case
  • E2E tests
  • duplication reduction in tests
  • removing GNSP reference

connectTimeout.set(extension.connectTimeout)
this.onlyIf { extension.useStaging.getOrElse(false) }
}
private val stagingRepositoryId: Property<String> = repository.stagingRepositoryId
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the best way to bind stagingRepositoryId in InitializeNexusStagingRepository and Repository.stagingRepositoryId, to be able to set value in task to set it in Extension/Repository. I initially just set stagingRepositoryId: Property<String> = repository.stagingRepositoryId, but I don't like sharing a reference, so maybe you have any better idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= objects.property().convention(repository.stagingRepositoryId) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, Unless I don't understand convention() properly, I will not be able to propagate value back to repository once it is set in the task. I need the other way round connection: task -> repository.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I understand your intention.

Can you please clarify how do you mean the task should be used?
I think the user does not know stagingRepositoryId before the task is executed, so I don't see how the user would configure the property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User could override the property in extension.repository to for example close or releate a requested staging repository with id known from the previous (failed) closing operation.

Copy link
Contributor Author

@szpak szpak Oct 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How problematic would it be to do the clean up in the second call? E.g.

gw release* -PyourID* && gw dropAllRepositories*

(assuming there will be that task one day)

While technically it is possible to call close/release/drop commands in Nexus with multiple repository IDs it's not a case with simple init/publish/close/release use case. On the other hand I can't find any sensible obstacles to use ListProperty<String> in the extension and have init to set just one element (there could be also some helper method to set just one).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How problematic would it be to do the clean up in the second call?

Users are not always comfortable with storing passwords/keys, so "release / drop" might require the user to type the password (or provide it with environment/property).
So your example with && would be much more complicated, as there are multiple credentials involved. In Apache case the credentials are for dist.apache.org (SVN), repository.apache.org (Nexus), gitbox.apache.org (Git) all of which participate in the release candidate/release publish.

Having a single command helps there.

Then there's "error recovery". If the actions are split into multiple commands, it makes it more complicated to "resume after failure". It looks like the user would have to analyze which command failed in order to retry or rollback.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this property in this class at all? Wouldn't it suffice to set it in NexusRepository? In addition, I think we should write it to a file and read it from there in NexusRepository by default. That way, one could initialize and publish in the first Gradle invocation, do some manual testing, and close and release without passing the stagingRepositoryId manually. Thought?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently NexusRepository is only passed to the constructor to collect some required values. The reference is not kept. However, if you are fine with that I will refactor the code.

Regarding keeping it in the file I propose to postpone it to Milestone 2 - it's not MVP.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

@Disabled("Broken - must run only after publishMavenJavaPublicationToSonatypeRepository not publishToSonatypeRepository")
internal fun `close task should run after related publish`() {
initSingleProjectWithDefaultConfiguration()
assertGivenTaskMustRunAfterAnother("closeSonatypeStagingRepository", "publishToSonatypeRepository")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

publishToNexusTask (and close/release tasks) depends/mustRunAfter publishMavenJavaPublicationToSonatypeRepository. It would be probably better to use publishToSonatypeRepository to also cover PDF documentation update, CDC stubs, etc. @marcphilipp How would be tweak it to cover that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid I don't understand your question. Could you please elaborate a bit more?

Copy link
Contributor Author

@szpak szpak Nov 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. How the easiest would be to change the plugin implementation to make closeSonatypeStagingRepository depends on publishToSonatypeRepository instead of all the related publications separately (e.g. publishMavenJavaPublicationToSonatypeRepository, publishPdfDocumentationPublicationToSonatypeRepository)?

It's not a big deal though as a publication for PDF documents should be also covered by:

        val publishTasks = project.tasks
                .withType<PublishToMavenRepository>()
                .matching { it.repository == nexusRepository }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just add closeTask.mustRunAfter(publishToNexusTask) and the same for releaseTask?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would be best to get a reference/provider to the umbrella publish task in the production plugin code?

@szpak
Copy link
Contributor Author

szpak commented Oct 18, 2019

Hmm, is it possible to switch to "draft PR" once a PR is created?

@@ -97,6 +97,28 @@ class NexusClient(private val baseUrl: URI, username: String?, password: String?
}
}

fun closeStagingRepository(stagingRepositoryId: String) {
try {
val response = api.closeStagingRepo(Dto(StagingRepositoryToTransit(listOf(stagingRepositoryId), "Closed by io.github.gradle-nexus.publish-plugin Gradle plugin"))).execute()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there was a more-or-less meaningful (configurable?) comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please raise an issue for that. Could be implemented in the future - not an MVP.

try {
val response = api.closeStagingRepo(Dto(StagingRepositoryToTransit(listOf(stagingRepositoryId), "Closed by io.github.gradle-nexus.publish-plugin Gradle plugin"))).execute()
if (!response.isSuccessful) {
throw failure("close staging repository", response)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should failure message include the repository id / group id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to make error handling for general for all the calls.

Copy link
Member

@marcphilipp marcphilipp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't have time for a full review, yet, but added some comments. I'll continue tomorrow. 🙂

build.gradle.kts Outdated Show resolved Hide resolved
protected val clientTimeout: Property<Duration> = objects.property()

@get:Internal
protected val connectTimeout: Property<Duration> = objects.property()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Maybe we should extract these properties into a @Nested NexusConfiguration object instead of using a base class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could want to have a separated timeout per repository. However, there still should be an easy way to set it globally (as in most of the cases it is enough - in fact most of the users (except @vlsi ;) ) will only use one repository per build).

@marcphilipp marcphilipp self-requested a review October 23, 2019 18:10
@Disabled("Broken - must run only after publishMavenJavaPublicationToSonatypeRepository not publishToSonatypeRepository")
internal fun `close task should run after related publish`() {
initSingleProjectWithDefaultConfiguration()
assertGivenTaskMustRunAfterAnother("closeSonatypeStagingRepository", "publishToSonatypeRepository")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid I don't understand your question. Could you please elaborate a bit more?

connectTimeout.set(extension.connectTimeout)
this.onlyIf { extension.useStaging.getOrElse(false) }
}
private val stagingRepositoryId: Property<String> = repository.stagingRepositoryId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this property in this class at all? Wouldn't it suffice to set it in NexusRepository? In addition, I think we should write it to a file and read it from there in NexusRepository by default. That way, one could initialize and publish in the first Gradle invocation, do some manual testing, and close and release without passing the stagingRepositoryId manually. Thought?

@szpak szpak requested a review from marcphilipp November 4, 2019 21:20
marcphilipp and others added 4 commits November 5, 2019 20:42
See: diffplug/spotless#476
As a side effect import-ordering has been disabled to do not break the build.
It changed to fail after upgrade. I don't want to mix that PR with global
import reordering.
@get:Internal
protected val connectTimeout: Property<Duration> = objects.property()

private val repository: Property<NexusRepository> = objects.property()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcphilipp Have you thought about something like to that to keep NexusRepository in a task? Our previous conversation disappeared :-/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we could use @Nested and remove all the fields that just copy its properties. Doing both looks redundant to me.

val client = NexusClient(repository.get().nexusUrl.get(), repository.get().username.orNull, repository.get().password.orNull, clientTimeout.orNull, connectTimeout.orNull)
val stagingProfileId = determineAndCacheStagingProfileId(client)
logger.info("Closing staging repository with id '{}' for stagingProfileId '{}'", stagingRepositoryConfig.get().repositoryId, stagingProfileId)
client.closeStagingRepository(stagingRepositoryConfig.get().repositoryId!!) //should be checked by @Input
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel comfortable with using !! and mutable structures (with nullable fields) at all. However, those 2 properties can be set independently and in addition they can be null (in some time periods - set by the earlier task execution or overridden with @Option). Nevertheless, I would like to hear any constructive proposal how to deal with it in some "better way".

- Remove stagingProfileId from close/release tasks as it is not needed
- Wire stagingRepositoryId for close/release task in plugin
- Remove mutable state from NexusRepository
- Rename CLI option to --staging-repository-id to make it more idiomatic
@szpak
Copy link
Contributor Author

szpak commented Jan 12, 2020

Applied @marcphilipp changes from #11.

@szpak
Copy link
Contributor Author

szpak commented Jan 12, 2020

Merging conflicts due to the recent changes (spotless upgrade and import reordering) in master :-/. Will fix.

@marcphilipp
Copy link
Member

Sorry! 😬

@@ -40,7 +39,6 @@ constructor(
private val stagingRepositoryId: (String) -> Unit
) : AbstractNexusStagingRepositoryTask(objects, extension, repository) {

@Optional
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only required if stagingProfileId is not set. Thus, I think @Optional was correct, wasn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, they both can be not set and then there will be val packageGroup = packageGroup.get() called. However, it would be problematic to add two-field present validation, so probably packageGroup.get() is good enough, especially as it reports the name of the not provided property. I will restore @Optional.

@szpak szpak merged commit 5b2c8ce into master Jan 23, 2020
@szpak szpak changed the title [WIP] [#4][#6] Close and release tasks [#4][#6] Close and release tasks Feb 5, 2020
@marcphilipp marcphilipp added this to the 0.1.0 milestone May 31, 2020
@marcphilipp marcphilipp deleted the closeAndRelease branch November 14, 2021 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants