Skip to content

Commit ba9d6f5

Browse files
committed
Allow central registry declaration similar to central repository declaration
Add OciSettingsExtension
1 parent aab99a2 commit ba9d6f5

5 files changed

Lines changed: 178 additions & 45 deletions

File tree

src/main/kotlin/io/github/sgtsilvio/gradle/oci/OciPlugin.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@ package io.github.sgtsilvio.gradle.oci
33
import io.github.sgtsilvio.gradle.oci.attributes.*
44
import io.github.sgtsilvio.gradle.oci.dsl.OciExtension
55
import io.github.sgtsilvio.gradle.oci.dsl.OciImageDefinition
6+
import io.github.sgtsilvio.gradle.oci.dsl.OciSettingsExtension
67
import io.github.sgtsilvio.gradle.oci.image.LoadOciImageTask
78
import io.github.sgtsilvio.gradle.oci.image.OciImageLayoutTask
89
import io.github.sgtsilvio.gradle.oci.image.OciImagesLayoutTask
910
import io.github.sgtsilvio.gradle.oci.image.PushOciImageTask
1011
import io.github.sgtsilvio.gradle.oci.internal.createOciImageLayoutClassifier
1112
import io.github.sgtsilvio.gradle.oci.internal.dsl.OciExtensionImpl
13+
import io.github.sgtsilvio.gradle.oci.internal.dsl.OciSettingsExtensionImpl
1214
import io.github.sgtsilvio.gradle.oci.internal.mainToEmpty
1315
import io.github.sgtsilvio.gradle.oci.internal.string.camelCase
1416
import io.github.sgtsilvio.gradle.oci.internal.string.concatCamelCase
1517
import org.gradle.api.Plugin
1618
import org.gradle.api.Project
1719
import org.gradle.api.UnknownTaskException
20+
import org.gradle.api.initialization.Settings
1821
import org.gradle.api.tasks.bundling.Tar
1922
import org.gradle.kotlin.dsl.add
2023
import org.gradle.kotlin.dsl.create
@@ -24,9 +27,27 @@ import org.gradle.kotlin.dsl.register
2427
/**
2528
* @author Silvio Giebl
2629
*/
27-
class OciPlugin : Plugin<Project> {
30+
class OciPlugin : Plugin<Any> {
2831

29-
override fun apply(project: Project) {
32+
override fun apply(target: Any) {
33+
when (target) {
34+
is Settings -> apply(target)
35+
is Project -> apply(target)
36+
else -> throw IllegalArgumentException("The plugin must be applied to a Project or Settings, but was applied to ${target::class.java}")
37+
}
38+
}
39+
40+
private fun apply(settings: Settings) {
41+
settings.extensions.create(
42+
OciSettingsExtension::class,
43+
EXTENSION_NAME,
44+
OciSettingsExtensionImpl::class,
45+
settings.dependencyResolutionManagement.repositories,
46+
settings.providers,
47+
)
48+
}
49+
50+
private fun apply(project: Project) {
3051
project.dependencies.attributesSchema.apply {
3152
attribute(DISTRIBUTION_TYPE_ATTRIBUTE)
3253
getMatchingStrategy(DISTRIBUTION_TYPE_ATTRIBUTE).apply {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.github.sgtsilvio.gradle.oci.dsl
2+
3+
import io.github.sgtsilvio.gradle.oci.mapping.OciImageMapping
4+
import org.gradle.api.Action
5+
6+
/**
7+
* @author Silvio Giebl
8+
*/
9+
interface OciSettingsExtension {
10+
val registries: OciRegistries
11+
val imageMapping: OciImageMapping
12+
13+
fun registries(configuration: Action<in OciRegistries>)
14+
15+
fun imageMapping(configuration: Action<in OciImageMapping>)
16+
}

src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/dsl/OciExtensionImpl.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ import io.github.sgtsilvio.gradle.oci.mapping.OciImageMapping
1111
import io.github.sgtsilvio.gradle.oci.mapping.OciImageMappingImpl
1212
import io.github.sgtsilvio.gradle.oci.platform.PlatformFilter
1313
import org.gradle.api.Action
14+
import org.gradle.api.Project
15+
import org.gradle.api.artifacts.ConfigurationContainer
1416
import org.gradle.api.file.ProjectLayout
1517
import org.gradle.api.model.ObjectFactory
1618
import org.gradle.api.plugins.jvm.JvmTestSuite
1719
import org.gradle.api.provider.Provider
1820
import org.gradle.api.provider.ProviderFactory
21+
import org.gradle.api.services.BuildServiceRegistry
1922
import org.gradle.api.tasks.TaskContainer
2023
import org.gradle.api.tasks.TaskProvider
2124
import org.gradle.api.tasks.testing.Test
@@ -32,11 +35,14 @@ internal abstract class OciExtensionImpl @Inject constructor(
3235
private val objectFactory: ObjectFactory,
3336
private val taskContainer: TaskContainer,
3437
private val projectLayout: ProjectLayout,
38+
buildServiceRegistry: BuildServiceRegistry,
39+
project: Project,
40+
configurationContainer: ConfigurationContainer,
3541
) : OciExtension {
3642

37-
final override val imageMapping = objectFactory.newInstance<OciImageMappingImpl>()
43+
final override val registries = objectFactory.newInstance<OciRegistriesImpl>()
3844

39-
final override val registries = objectFactory.newInstance<OciRegistriesImpl>(imageMapping)
45+
final override val imageMapping = objectFactory.newInstance<OciImageMappingImpl>()
4046

4147
final override val imageDefinitions = objectFactory.domainObjectContainer(OciImageDefinition::class) { name ->
4248
objectFactory.newInstance<OciImageDefinitionImpl>(name, parentImageDependencies)
@@ -54,6 +60,7 @@ internal abstract class OciExtensionImpl @Inject constructor(
5460
init {
5561
// eagerly realize imageDefinitions because they register configurations and tasks
5662
imageDefinitions.all {}
63+
setupProjectOciRegistries(buildServiceRegistry, project, configurationContainer, registries, imageMapping)
5764
}
5865

5966
final override fun registries(configuration: Action<in OciRegistries>) = configuration.execute(registries)

src/main/kotlin/io/github/sgtsilvio/gradle/oci/internal/dsl/OciRegistriesImpl.kt

Lines changed: 94 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.netty.buffer.UnpooledByteBufAllocator
1616
import io.netty.channel.ChannelOption
1717
import io.netty.util.concurrent.FastThreadLocal
1818
import org.gradle.api.Action
19+
import org.gradle.api.NamedDomainObjectList
1920
import org.gradle.api.Project
2021
import org.gradle.api.artifacts.ConfigurationContainer
2122
import org.gradle.api.artifacts.ResolvableDependencies
@@ -50,30 +51,12 @@ internal val OCI_IMAGE_DISTRIBUTION_TYPES = arrayOf(OCI_IMAGE_DISTRIBUTION_TYPE,
5051
* @author Silvio Giebl
5152
*/
5253
internal abstract class OciRegistriesImpl @Inject constructor(
53-
private val imageMapping: OciImageMappingImpl,
54-
private val objectFactory: ObjectFactory,
5554
private val repositoryHandler: RepositoryHandler,
56-
configurationContainer: ConfigurationContainer,
57-
buildServiceRegistry: BuildServiceRegistry,
58-
project: Project,
55+
private val objectFactory: ObjectFactory,
56+
private val providerFactory: ProviderFactory,
5957
) : OciRegistries {
6058
final override val list = objectFactory.namedDomainObjectList(OciRegistry::class)
6159
final override val repositoryPort: Property<Int> = objectFactory.property<Int>().convention(5123)
62-
private val registriesService =
63-
buildServiceRegistry.registerIfAbsent("ociRegistriesService-${project.path}", OciRegistriesService::class) {}
64-
65-
private var beforeResolveInitialized = false
66-
67-
init {
68-
configurationContainer.configureEach {
69-
incoming.beforeResolve {
70-
if (!beforeResolveInitialized && resolvesOciImages()) {
71-
beforeResolveInitialized = true
72-
beforeResolve()
73-
}
74-
}
75-
}
76-
}
7760

7861
final override fun registry(name: String, configuration: Action<in OciRegistry>): OciRegistry {
7962
val registry = getOrCreateRegistry(name)
@@ -100,7 +83,7 @@ internal abstract class OciRegistriesImpl @Inject constructor(
10083
private inline fun getOrCreateRegistry(name: String, init: OciRegistry.() -> Unit = {}): OciRegistry {
10184
var registry = list.findByName(name)
10285
if (registry == null) {
103-
registry = objectFactory.newInstance<OciRegistryImpl>(name, this)
86+
registry = objectFactory.newInstance<OciRegistryImpl>(name, this, repositoryHandler, providerFactory)
10487
registry.init()
10588
list += registry
10689
}
@@ -113,28 +96,14 @@ internal abstract class OciRegistriesImpl @Inject constructor(
11396
filter(configuration)
11497
}
11598
}
116-
117-
private fun ResolvableDependencies.resolvesOciImages() =
118-
attributes.getAttribute(DISTRIBUTION_TYPE_ATTRIBUTE) in OCI_IMAGE_DISTRIBUTION_TYPES
119-
120-
private fun beforeResolve() {
121-
if (list.isNotEmpty()) {
122-
val registriesService = registriesService.get()
123-
registriesService.init(repositoryPort.get())
124-
val imageMappingData = imageMapping.getData()
125-
for (registry in list) {
126-
registriesService.register(registry, imageMappingData)
127-
}
128-
}
129-
}
13099
}
131100

132101
internal abstract class OciRegistryImpl @Inject constructor(
133102
private val name: String,
134103
registries: OciRegistriesImpl,
104+
repositoryHandler: RepositoryHandler,
135105
objectFactory: ObjectFactory,
136106
private val providerFactory: ProviderFactory,
137-
repositoryHandler: RepositoryHandler,
138107
) : OciRegistry {
139108

140109
final override val url = objectFactory.property<URI>()
@@ -167,26 +136,67 @@ internal abstract class OciRegistryImpl @Inject constructor(
167136
repository.content(configuration)
168137
}
169138

139+
private const val SERVICE_BASE_NAME = "ociRegistriesService"
170140
private const val PORT_HTTP_HEADER_NAME = "port"
171141

142+
internal fun OciRegistriesService(
143+
buildServiceRegistry: BuildServiceRegistry,
144+
name: String,
145+
registries: NamedDomainObjectList<OciRegistry>,
146+
repositoryPort: Property<Int>,
147+
imageMapping: OciImageMappingImpl,
148+
): OciRegistriesService {
149+
val registriesService = buildServiceRegistry.registerIfAbsent(name, OciRegistriesService::class) {}.get()
150+
registriesService.init(registries, repositoryPort, imageMapping)
151+
return registriesService
152+
}
153+
172154
internal abstract class OciRegistriesService : BuildService<BuildServiceParameters.None>, AutoCloseable {
155+
lateinit var registries: NamedDomainObjectList<OciRegistry>
156+
lateinit var repositoryPort: Property<Int>
157+
lateinit var imageMapping: OciImageMappingImpl
158+
private var isStarted = false
173159
private val httpServers = mutableListOf<DisposableServer>()
174160
private val loopResources = OciLoopResources.acquire()
175161
private val imageMetadataRegistry = OciImageMetadataRegistry(OciRegistryApi(OciRegistryHttpClient.acquire()))
176162

177-
fun init(port: Int) {
163+
fun init(
164+
registries: NamedDomainObjectList<OciRegistry>,
165+
repositoryPort: Property<Int>,
166+
imageMapping: OciImageMappingImpl,
167+
) {
168+
this.registries = registries
169+
this.repositoryPort = repositoryPort
170+
this.imageMapping = imageMapping
171+
}
172+
173+
fun start() {
174+
if (isStarted) {
175+
return
176+
}
177+
isStarted = true
178+
if (registries.isNotEmpty()) {
179+
startRedirectServer(repositoryPort.get())
180+
val imageMappingData = imageMapping.getData()
181+
for (registry in registries) {
182+
startRegistryServer(registry, imageMappingData)
183+
}
184+
}
185+
}
186+
187+
private fun startRedirectServer(port: Int) {
178188
try {
179-
addHttpServer(port) { request, response ->
189+
startHttpServer(port) { request, response ->
180190
val redirectPort = request.requestHeaders()[PORT_HTTP_HEADER_NAME]
181191
response.sendRedirect("http://localhost:" + redirectPort + request.uri())
182192
}
183193
} catch (_: ChannelBindException) {
184194
}
185195
}
186196

187-
fun register(registry: OciRegistry, imageMappingData: OciImageMappingData) {
197+
private fun startRegistryServer(registry: OciRegistry, imageMappingData: OciImageMappingData) {
188198
val credentials = registry.credentials.orNull?.let { Credentials(it.username!!, it.password!!) }
189-
val port = addHttpServer(0, OciRepositoryHandler(imageMetadataRegistry, imageMappingData, credentials)).port()
199+
val port = startHttpServer(0, OciRepositoryHandler(imageMetadataRegistry, imageMappingData, credentials)).port()
190200
registry.repository.credentials(HttpHeaderCredentials::class) {
191201
name = PORT_HTTP_HEADER_NAME
192202
value = port.toString()
@@ -196,7 +206,7 @@ internal abstract class OciRegistriesService : BuildService<BuildServiceParamete
196206
}
197207
}
198208

199-
private fun addHttpServer(
209+
private fun startHttpServer(
200210
port: Int,
201211
handler: BiFunction<in HttpServerRequest, in HttpServerResponse, out Publisher<Void>>,
202212
): DisposableServer {
@@ -225,3 +235,46 @@ internal abstract class OciRegistriesService : BuildService<BuildServiceParamete
225235
OciLoopResources.release()
226236
}
227237
}
238+
239+
internal fun setupSettingsOciRegistries(
240+
buildServiceRegistry: BuildServiceRegistry,
241+
registries: OciRegistries,
242+
imageMapping: OciImageMappingImpl,
243+
) {
244+
OciRegistriesService(
245+
buildServiceRegistry,
246+
SERVICE_BASE_NAME,
247+
registries.list,
248+
registries.repositoryPort,
249+
imageMapping,
250+
)
251+
}
252+
253+
internal fun setupProjectOciRegistries(
254+
buildServiceRegistry: BuildServiceRegistry,
255+
project: Project,
256+
configurationContainer: ConfigurationContainer,
257+
registries: OciRegistries,
258+
imageMapping: OciImageMappingImpl,
259+
) {
260+
configurationContainer.configureEach {
261+
incoming.beforeResolve {
262+
if (resolvesOciImages()) {
263+
val settingsRegistration = buildServiceRegistry.registrations.findByName(SERVICE_BASE_NAME)
264+
if (settingsRegistration != null) {
265+
(settingsRegistration.service.get() as OciRegistriesService).start()
266+
}
267+
OciRegistriesService(
268+
buildServiceRegistry,
269+
"$SERVICE_BASE_NAME-${project.path}",
270+
registries.list,
271+
registries.repositoryPort,
272+
imageMapping,
273+
).start()
274+
}
275+
}
276+
}
277+
}
278+
279+
private fun ResolvableDependencies.resolvesOciImages() =
280+
attributes.getAttribute(DISTRIBUTION_TYPE_ATTRIBUTE) in OCI_IMAGE_DISTRIBUTION_TYPES
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.github.sgtsilvio.gradle.oci.internal.dsl
2+
3+
import io.github.sgtsilvio.gradle.oci.dsl.OciRegistries
4+
import io.github.sgtsilvio.gradle.oci.dsl.OciSettingsExtension
5+
import io.github.sgtsilvio.gradle.oci.mapping.OciImageMapping
6+
import io.github.sgtsilvio.gradle.oci.mapping.OciImageMappingImpl
7+
import org.gradle.api.Action
8+
import org.gradle.api.artifacts.dsl.RepositoryHandler
9+
import org.gradle.api.model.ObjectFactory
10+
import org.gradle.api.provider.ProviderFactory
11+
import org.gradle.api.services.BuildServiceRegistry
12+
import org.gradle.kotlin.dsl.newInstance
13+
import javax.inject.Inject
14+
15+
/**
16+
* @author Silvio Giebl
17+
*/
18+
internal abstract class OciSettingsExtensionImpl @Inject constructor(
19+
repositoryHandler: RepositoryHandler,
20+
objectFactory: ObjectFactory,
21+
providerFactory: ProviderFactory,
22+
buildServiceRegistry: BuildServiceRegistry,
23+
) : OciSettingsExtension {
24+
25+
final override val registries = objectFactory.newInstance<OciRegistriesImpl>(repositoryHandler, providerFactory)
26+
27+
final override val imageMapping = objectFactory.newInstance<OciImageMappingImpl>()
28+
29+
init {
30+
setupSettingsOciRegistries(buildServiceRegistry, registries, imageMapping)
31+
}
32+
33+
final override fun registries(configuration: Action<in OciRegistries>) = configuration.execute(registries)
34+
35+
final override fun imageMapping(configuration: Action<in OciImageMapping>) = configuration.execute(imageMapping)
36+
}

0 commit comments

Comments
 (0)