OpenSearch/plugin/ml-cpp-snapshot/build.gradle

171 lines
6.8 KiB
Groovy

import com.amazonaws.AmazonServiceException
import com.amazonaws.ClientConfiguration
import com.amazonaws.auth.AWSCredentials
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.services.s3.AmazonS3Client
import com.amazonaws.services.s3.model.S3Object
import com.amazonaws.services.s3.model.ObjectMetadata
import com.bettercloud.vault.Vault
import com.bettercloud.vault.VaultConfig
import com.bettercloud.vault.response.LogicalResponse
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.PosixFilePermissions
import org.elasticsearch.gradle.VersionProperties
apply plugin: 'distribution'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'com.bettercloud', name: 'vault-java-driver', version:"1.1.0"
classpath 'com.amazonaws:aws-java-sdk-s3:1.10.33'
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
classpath 'com.sun.xml.bind:jaxb-impl:2.2.3-1' // pulled in as external dependency to work on java 9
}
}
}
ext.version = VersionProperties.elasticsearch
// This project pulls a snapshot version of the ML cpp artifacts and sets that as the artifact
// for this project so it can be used with dependency substitution. We do not use gradle's
// handling of S3 as a maven repo due to the dynamically generated creds being slow to propagate,
// necessitating retries.
void checkJavaVersion() {
/**
* The Elastic Secrets vault is served via HTTPS with a Let's Encrypt certificate. The root certificates that cross-signed the Let's
* Encrypt certificates were not trusted by the JDK until 8u101. Therefore, we enforce that the JDK is at least 8u101 here.
*/
final String javaVersion = System.getProperty('java.version')
final String javaVendor = System.getProperty('java.vendor')
def matcher = javaVersion =~ /1\.8\.0(?:_(\d+))?/
boolean matches = matcher.matches()
if (matches) {
final int update
if (matcher.group(1) == null) {
update = 0
} else {
update = matcher.group(1).toInteger()
}
if (update < 101) {
throw new GradleException("JDK ${javaVendor} ${javaVersion} does not have necessary root certificates " +
"(https://bugs.openjdk.java.net/browse/JDK-8154757), update your JDK to at least JDK 8u101+")
}
}
}
void setupVaultAuthMethod() {
String VAULT_BASE_URL = 'https://secrets.elastic.co:8200'
String VAULT_ROLE_ID = "8e90dd88-5a8e-9c12-0da9-5439f293ff97"
String VAULT_SECRET_ID = System.env.VAULT_SECRET_ID
// get an authentication token with vault
String homePath = System.properties['user.home']
File githubToken = file("${homePath}/.elastic/github.token")
String vaultAuthBody = null
URL vaultUrl = null
if (githubToken.exists()) {
try {
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(githubToken.toPath())
if (perms.equals(PosixFilePermissions.fromString("rw-------")) == false) {
throw new GradleException('github.token must have 600 permissions')
}
} catch (UnsupportedOperationException e) {
// Assume this isn't a POSIX file system
}
vaultUrl = new URL(VAULT_BASE_URL + '/v1/auth/github/login')
vaultAuthBody = "{\"token\": \"${githubToken.getText('UTF-8').trim()}\"}"
} else if (VAULT_SECRET_ID != null) {
vaultUrl = new URL(VAULT_BASE_URL + '/v1/auth/approle/login')
vaultAuthBody = "{\"role_id\": \"${VAULT_ROLE_ID}\", \"secret_id\": \"${VAULT_SECRET_ID}\"}"
} else {
throw new GradleException('Missing ~/.elastic/github.token file or VAULT_SECRET_ID environment variable, needed to authenticate with vault for secrets')
}
project.ext.vaultAuthBody = vaultAuthBody
project.ext.vaultUrl = vaultUrl
}
void getZip(File snapshotZip) {
HttpURLConnection vaultConn = (HttpURLConnection) vaultUrl.openConnection()
vaultConn.setRequestProperty('Content-Type', 'application/json')
vaultConn.setRequestMethod('PUT')
vaultConn.setDoOutput(true)
vaultConn.outputStream.withWriter('UTF-8') { writer ->
writer.write(vaultAuthBody)
}
vaultConn.connect()
Object authResponse = new groovy.json.JsonSlurper().parseText(vaultConn.content.text)
VaultConfig config = new VaultConfig('https://secrets.elastic.co:8200', authResponse.auth.client_token)
Vault vault = new Vault(config)
LogicalResponse secret = vault.logical().read("aws-dev/creds/prelertartifacts")
final AWSCredentials creds = new BasicAWSCredentials(secret.data.get('access_key'), secret.data.get('secret_key'))
// the keys may take a while to propagate, so wait up to 60 seconds retrying
final AmazonS3Client client = new AmazonS3Client(creds)
final String key = "maven/org/elasticsearch/ml/ml-cpp/${version}/ml-cpp-${version}.zip"
int retries = 120
while (retries > 0) {
try {
File snapshotMd5 = new File(snapshotZip.toString() + '.md5')
// do a HEAD first to check the zip hash against the local file
ObjectMetadata metadata = client.getObjectMetadata('prelert-artifacts', key)
String remoteMd5 = metadata.getETag()
if (snapshotZip.exists()) {
// do a HEAD first to check the zip hash against the local file
String localMd5 = snapshotMd5.getText('UTF-8')
if (remoteMd5.equals(localMd5)) {
logger.info('Using cached ML snapshot')
return
}
}
S3Object zip = client.getObject('prelert-artifacts', key)
InputStream zipStream = zip.getObjectContent()
try {
project.delete(snapshotZip, snapshotZip)
Files.copy(zipStream, snapshotZip.toPath())
} finally {
zipStream.close()
}
snapshotMd5.setText(remoteMd5, 'UTF-8')
return
} catch (AmazonServiceException e) {
if (e.getStatusCode() != 403) {
throw new GradleException('Error while trying to get ml-cpp snapshot: ' + e.getMessage(), e)
}
sleep(500)
retries--
}
}
throw new GradleException('Could not access ml-cpp artifacts. Timed out after 60 seconds')
}
File snapshotZip = new File(projectDir, ".cache/ml-cpp-${version}.zip")
task downloadMachineLearningSnapshot {
onlyIf {
// skip if machine-learning-cpp is being built locally
findProject(':machine-learning-cpp') == null &&
// skip for offline builds - just rely on the artifact already having been downloaded before here
project.gradle.startParameter.isOffline() == false
}
doFirst {
snapshotZip.parentFile.mkdirs()
getZip(snapshotZip)
}
}
gradle.taskGraph.whenReady { taskGraph ->
// skip if machine-learning-cpp is being built locally and also for offline builds
if (findProject(':machine-learning-cpp') == null && project.gradle.startParameter.isOffline() == false) {
// do validation of token/java version up front, don't wait for the task to run
checkJavaVersion()
setupVaultAuthMethod()
}
}
artifacts {
'default' file: snapshotZip, name: 'ml-cpp', type: 'zip', builtBy: downloadMachineLearningSnapshot
}