diff --git a/igor-core/igor-core.gradle b/igor-core/igor-core.gradle index 5827208a8..1f5822110 100644 --- a/igor-core/igor-core.gradle +++ b/igor-core/igor-core.gradle @@ -1,7 +1,4 @@ dependencies { - implementation "org.springframework.boot:spring-boot-starter-web" - - implementation "com.netflix.spinnaker.fiat:fiat-core:$fiatVersion" implementation "com.netflix.spinnaker.kork:kork-artifacts" implementation "com.netflix.spinnaker.kork:kork-core" diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/IgorConfigurationProperties.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/IgorConfigurationProperties.java index a0cc0a0ff..2afad5203 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/IgorConfigurationProperties.java +++ b/igor-core/src/main/java/com/netflix/spinnaker/igor/IgorConfigurationProperties.java @@ -67,7 +67,7 @@ public static class BuildProperties { */ private int pollInterval = 60; - /** TODO(jc): Please document TODO(rz): Duration */ + /** TODO(jc): Please document */ private int lookBackWindowMins = 10 * 60 * 60; /** TODO(jc): Please document */ diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/GenericGitRevision.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/GenericGitRevision.java index 84dd0d0a2..76bbf8455 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/GenericGitRevision.java +++ b/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/GenericGitRevision.java @@ -22,7 +22,6 @@ import lombok.Getter; import lombok.experimental.Wither; -/** TODO(rz): Rename to GitRevision. */ @Getter @EqualsAndHashCode(of = "sha1") @Builder diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/ArtifactoryEvent.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/ArtifactoryEvent.java index aaf2856dc..1e60aff79 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/ArtifactoryEvent.java +++ b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/ArtifactoryEvent.java @@ -21,11 +21,13 @@ import java.util.Map; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; +@EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor @Data -public class ArtifactoryEvent implements Event { +public class ArtifactoryEvent extends Event { private final Content content; private final Map details = ImmutableMap.builder() diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/DockerEvent.groovy b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/DockerEvent.groovy index 7b45dc96a..72cc91819 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/DockerEvent.groovy +++ b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/DockerEvent.groovy @@ -18,7 +18,7 @@ package com.netflix.spinnaker.igor.history.model import com.netflix.spinnaker.igor.build.model.GenericArtifact -class DockerEvent implements Event { +class DockerEvent extends Event { Content content GenericArtifact artifact diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/EmptyBuildContent.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/EmptyBuildContent.java deleted file mode 100644 index 2fa632d8f..000000000 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/EmptyBuildContent.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.netflix.spinnaker.igor.history.model; - -import lombok.AllArgsConstructor; -import lombok.Data; - -/** Move any invocation of this class to a monitor-specific BuildContent implementation. */ -@Deprecated -@Data -@AllArgsConstructor -public class EmptyBuildContent implements BuildContent { - - public static String TYPE = "empty"; - - @Override - public String getType() { - return TYPE; - } -} diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/TestResult.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/Event.groovy similarity index 83% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/TestResult.java rename to igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/Event.groovy index be35328db..f086dc250 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/TestResult.java +++ b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/Event.groovy @@ -1,7 +1,7 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2016 Google, Inc. * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.build.model; -public interface TestResult {} +package com.netflix.spinnaker.igor.history.model + +abstract class Event { +} diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/Event.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/Event.java deleted file mode 100644 index 17c11edab..000000000 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/Event.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.netflix.spinnaker.igor.history.model; - -/** TODO(rz): Document. */ -public interface Event {} diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/GenericBuildEvent.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/GenericBuildEvent.java deleted file mode 100644 index 02db0126c..000000000 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/GenericBuildEvent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.netflix.spinnaker.igor.history.model; - -import java.util.Map; -import lombok.AllArgsConstructor; -import lombok.Data; - -/** Move any invocation of this class to a monitor-specific BuildContent implementation. */ -@Deprecated -@Data -@AllArgsConstructor -public class GenericBuildEvent implements BuildEvent { - private EmptyBuildContent content; - private Map details; - - public GenericBuildEvent(EmptyBuildContent content) { - this.content = content; - } -} diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/polling/PollingMonitor.java b/igor-core/src/main/java/com/netflix/spinnaker/igor/polling/PollingMonitor.java index 7aaa84319..1a0dfd16f 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/polling/PollingMonitor.java +++ b/igor-core/src/main/java/com/netflix/spinnaker/igor/polling/PollingMonitor.java @@ -27,7 +27,6 @@ public interface PollingMonitor extends ApplicationListener extends BuildOperations { - /** Get the queued build at {@code queueId}. */ - T getQueuedBuild(String queueId); - - /** - * Stop a queued build at {@code queueId}. - * - *

Will not wait for a result. Must be idempotent. - */ - default void stopQueuedBuild(String jobName, String queueId, int buildNumber) { - // Do nothing. - } -} diff --git a/igor-monitor-jenkins/igor-monitor-jenkins.gradle b/igor-monitor-jenkins/igor-monitor-jenkins.gradle deleted file mode 100644 index 313bbb5d0..000000000 --- a/igor-monitor-jenkins/igor-monitor-jenkins.gradle +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -dependencies { - implementation project(":igor-core") - - compileOnly "org.projectlombok:lombok" - annotationProcessor platform("com.netflix.spinnaker.kork:kork-bom:$korkVersion") - annotationProcessor "org.projectlombok:lombok" - testAnnotationProcessor platform("com.netflix.spinnaker.kork:kork-bom:$korkVersion") - testAnnotationProcessor "org.projectlombok:lombok" - - implementation "org.springframework.boot:spring-boot-starter-web" - - implementation "com.netflix.spinnaker.fiat:fiat-api:$fiatVersion" - implementation "com.netflix.spinnaker.fiat:fiat-core:$fiatVersion" - implementation "com.netflix.spinnaker.kork:kork-security" - implementation "com.netflix.spinnaker.kork:kork-telemetry" - - implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml" - implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" - - implementation "com.squareup.okhttp:okhttp" - implementation "com.squareup.retrofit:converter-jackson" - implementation "com.google.guava:guava" - - testImplementation "com.squareup.okhttp:mockwebserver" - - // TODO(rz): Get rid of this dependency! - implementation "com.netflix.spinnaker.kork:kork-jedis" - - // TODO(rz): Get rid of this dependency! - implementation "com.squareup.retrofit:retrofit" - - // TODO(rz): Get rid of this dependency! - implementation "com.squareup.retrofit:converter-simplexml" - - // TODO(rz): Get rid of this dependency! - implementation "com.netflix.spinnaker.kork:kork-hystrix" - - // TODO(rz): Get rid of this dependency! - implementation 'com.google.auth:google-auth-library-oauth2-http' -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/JenkinsConfig.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/JenkinsConfig.java deleted file mode 100644 index 7d22f89f0..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/JenkinsConfig.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.netflix.spinnaker.igor.config; - -import static java.lang.String.format; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; -import com.google.common.base.Strings; -import com.netflix.spectator.api.Registry; -import com.netflix.spinnaker.fiat.model.resources.Permissions; -import com.netflix.spinnaker.igor.IgorConfigurationProperties; -import com.netflix.spinnaker.igor.config.client.DefaultJenkinsOkHttpClientProvider; -import com.netflix.spinnaker.igor.config.client.DefaultJenkinsRetrofitRequestInterceptorProvider; -import com.netflix.spinnaker.igor.config.client.JenkinsOkHttpClientProvider; -import com.netflix.spinnaker.igor.config.client.JenkinsRetrofitRequestInterceptorProvider; -import com.netflix.spinnaker.igor.jenkins.JenkinsService; -import com.netflix.spinnaker.igor.jenkins.client.JenkinsClient; -import com.netflix.spinnaker.igor.service.BuildServices; -import com.netflix.spinnaker.kork.telemetry.InstrumentedProxy; -import com.squareup.okhttp.OkHttpClient; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.*; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import javax.net.ssl.*; -import javax.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import retrofit.Endpoints; -import retrofit.RequestInterceptor; -import retrofit.RestAdapter; -import retrofit.client.OkClient; -import retrofit.converter.JacksonConverter; - -/** - * Converts the list of Jenkins Configuration properties a collection of clients to access the - * Jenkins hosts - */ -@Configuration -@Slf4j -@ConditionalOnProperty("jenkins.enabled") -@EnableConfigurationProperties(JenkinsProperties.class) -public class JenkinsConfig { - public static JenkinsService jenkinsService( - String jenkinsHostId, JenkinsClient jenkinsClient, Boolean csrf, Permissions permissions) { - return new JenkinsService(jenkinsHostId, jenkinsClient, csrf, permissions); - } - - public static ObjectMapper getObjectMapper() { - return new XmlMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .registerModule(new JaxbAnnotationModule()); - } - - public static JenkinsClient jenkinsClient( - JenkinsProperties.JenkinsHost host, - OkHttpClient client, - RequestInterceptor requestInterceptor, - int timeout) { - try { - return checkedJenkinsClient(host, client, requestInterceptor, timeout); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException( - format("Cannot configure jenkins client for host '%s'", host.getName()), e); - } - } - - public static JenkinsClient checkedJenkinsClient( - JenkinsProperties.JenkinsHost host, - OkHttpClient client, - RequestInterceptor requestInterceptor, - int timeout) - throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException, IOException, - CertificateException, UnrecoverableKeyException { - client.setReadTimeout(timeout, TimeUnit.MILLISECONDS); - - if (host.getSkipHostnameVerification()) { - client.setHostnameVerifier((hostname, session) -> true); - } - - TrustManager[] trustManagers = null; - KeyManager[] keyManagers = null; - - if (!Strings.isNullOrEmpty(host.getTrustStore())) { - if (host.getTrustStore().equals("*")) { - trustManagers = - new ArrayList<>(Collections.singletonList(new TrustAllTrustManager())) - .toArray(new TrustManager[0]); - } else { - String trustStorePassword = host.getTrustStorePassword(); - - KeyStore trustStore = KeyStore.getInstance(host.getTrustStoreType()); - trustStore.load( - new ByteArrayInputStream(Files.readAllBytes(Paths.get(host.getTrustStore()))), - trustStorePassword.toCharArray()); - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStore); - - trustManagers = trustManagerFactory.getTrustManagers(); - } - } - - if (!Strings.isNullOrEmpty(host.getKeyStore())) { - KeyStore keyStore = KeyStore.getInstance(host.getKeyStoreType()); - - keyStore.load( - new ByteArrayInputStream(Files.readAllBytes(Paths.get(host.getKeyStore()))), - host.getKeyStorePassword().toCharArray()); - - KeyManagerFactory keyManagerFactory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, host.getKeyStorePassword().toCharArray()); - - keyManagers = keyManagerFactory.getKeyManagers(); - } - - if (trustManagers != null || keyManagers != null) { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, trustManagers, null); - - client.setSslSocketFactory(sslContext.getSocketFactory()); - } - - return new RestAdapter.Builder() - .setEndpoint(Endpoints.newFixedEndpoint(host.getAddress())) - .setRequestInterceptor( - request -> { - request.addHeader("User-Agent", "Spinnaker-igor"); - requestInterceptor.intercept(request); - }) - .setClient(new OkClient(client)) - .setConverter(new JacksonConverter(getObjectMapper())) - .build() - .create(JenkinsClient.class); - } - - public static JenkinsClient jenkinsClient( - JenkinsProperties.JenkinsHost host, - OkHttpClient client, - RequestInterceptor requestInterceptor) { - return JenkinsConfig.jenkinsClient(host, client, requestInterceptor, 30000); - } - - public static JenkinsClient jenkinsClient(JenkinsProperties.JenkinsHost host, int timeout) { - OkHttpClient client = new OkHttpClient(); - return jenkinsClient(host, client, RequestInterceptor.NONE, timeout); - } - - public static JenkinsClient jenkinsClient(JenkinsProperties.JenkinsHost host) { - return JenkinsConfig.jenkinsClient(host, 30000); - } - - @Bean - @ConditionalOnMissingBean - public JenkinsOkHttpClientProvider jenkinsOkHttpClientProvider() { - return new DefaultJenkinsOkHttpClientProvider(); - } - - @Bean - @ConditionalOnMissingBean - public JenkinsRetrofitRequestInterceptorProvider jenkinsRetrofitRequestInterceptorProvider() { - return new DefaultJenkinsRetrofitRequestInterceptorProvider(); - } - - @Bean - public Map jenkinsMasters( - BuildServices buildServices, - final IgorConfigurationProperties igorConfigurationProperties, - @Valid JenkinsProperties jenkinsProperties, - final JenkinsOkHttpClientProvider jenkinsOkHttpClientProvider, - final JenkinsRetrofitRequestInterceptorProvider jenkinsRetrofitRequestInterceptorProvider, - final Registry registry) { - log.info("creating jenkinsMasters"); - - Map jenkinsMasters = - jenkinsProperties.getMasters().stream() - .filter(Objects::nonNull) - .collect( - Collectors.toMap( - (JenkinsProperties.JenkinsHost host) -> { - log.info("bootstrapping {} as {}", host.getAddress(), host.getName()); - return host.getName(); - }, - (JenkinsProperties.JenkinsHost host) -> { - JenkinsClient client = - InstrumentedProxy.proxy( - registry, - jenkinsClient( - host, - jenkinsOkHttpClientProvider.provide(host), - jenkinsRetrofitRequestInterceptorProvider.provide(host), - igorConfigurationProperties.getClient().getTimeout()), - "jenkinsClient", - Collections.singletonMap("master", host.getName())); - return jenkinsService( - host.getName(), client, host.getCsrf(), host.getPermissions().build()); - })); - - buildServices.addServices(jenkinsMasters); - return jenkinsMasters; - } - - public static class TrustAllTrustManager implements X509TrustManager { - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - // do nothing - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - // do nothing - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/JenkinsProperties.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/JenkinsProperties.java deleted file mode 100644 index d6643b564..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/JenkinsProperties.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.netflix.spinnaker.igor.config; - -import static com.netflix.spinnaker.igor.config.JenkinsProperties.*; - -import com.netflix.spinnaker.fiat.model.resources.Permissions; -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.List; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -/** Helper class to map masters in properties file into a validated property map */ -@ConfigurationProperties(prefix = "jenkins") -@Validated -public class JenkinsProperties implements BuildServerProperties { - @Valid private List masters; - - @Override - public List getMasters() { - return masters; - } - - public void setMasters(List masters) { - this.masters = masters; - } - - public static class JenkinsHost implements BuildServerProperties.Host { - @NotEmpty private String name; - @NotEmpty private String address; - private String username; - private String password; - private Boolean csrf = false; - private String jsonPath; - private List oauthScopes = new ArrayList(); - private String token; - private Integer itemUpperThreshold; - private String trustStore; - private String trustStoreType = KeyStore.getDefaultType(); - private String trustStorePassword; - private String keyStore; - private String keyStoreType = KeyStore.getDefaultType(); - private String keyStorePassword; - private Boolean skipHostnameVerification = false; - private Permissions.Builder permissions = new Permissions.Builder(); - - @Override - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public Boolean getCsrf() { - return csrf; - } - - public void setCsrf(Boolean csrf) { - this.csrf = csrf; - } - - public String getJsonPath() { - return jsonPath; - } - - public void setJsonPath(String jsonPath) { - this.jsonPath = jsonPath; - } - - public List getOauthScopes() { - return oauthScopes; - } - - public void setOauthScopes(List oauthScopes) { - this.oauthScopes = oauthScopes; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public Integer getItemUpperThreshold() { - return itemUpperThreshold; - } - - public void setItemUpperThreshold(Integer itemUpperThreshold) { - this.itemUpperThreshold = itemUpperThreshold; - } - - public String getTrustStore() { - return trustStore; - } - - public void setTrustStore(String trustStore) { - this.trustStore = trustStore; - } - - public String getTrustStoreType() { - return trustStoreType; - } - - public void setTrustStoreType(String trustStoreType) { - this.trustStoreType = trustStoreType; - } - - public String getTrustStorePassword() { - return trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public String getKeyStore() { - return keyStore; - } - - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - - public String getKeyStoreType() { - return keyStoreType; - } - - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public String getKeyStorePassword() { - return keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public Boolean getSkipHostnameVerification() { - return skipHostnameVerification; - } - - public void setSkipHostnameVerification(Boolean skipHostnameVerification) { - this.skipHostnameVerification = skipHostnameVerification; - } - - @Override - public Permissions.Builder getPermissions() { - return permissions; - } - - public void setPermissions(Permissions.Builder permissions) { - this.permissions = permissions; - } - } -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/auth/AuthRequestInterceptor.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/auth/AuthRequestInterceptor.java deleted file mode 100644 index 063fd294a..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/auth/AuthRequestInterceptor.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.netflix.spinnaker.igor.config.auth; - -import com.google.auth.oauth2.GoogleCredentials; -import com.google.common.base.Joiner; -import com.google.common.base.Strings; -import com.netflix.spinnaker.igor.config.JenkinsProperties; -import com.squareup.okhttp.Credentials; -import java.io.*; -import java.util.ArrayList; -import java.util.List; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import retrofit.RequestInterceptor; - -@Slf4j -public class AuthRequestInterceptor implements RequestInterceptor { - - private static final Joiner SUPPLIERS_JOINER = Joiner.on(", "); - - public AuthRequestInterceptor(JenkinsProperties.JenkinsHost host) { - // Order may be significant here. - if (!Strings.isNullOrEmpty(host.getUsername()) && !Strings.isNullOrEmpty(host.getPassword())) { - suppliers.add(new BasicAuthHeaderSupplier(host.getUsername(), host.getPassword())); - } - - if (!Strings.isNullOrEmpty(host.getJsonPath()) && !host.getOauthScopes().isEmpty()) { - suppliers.add(new GoogleBearerTokenHeaderSupplier(host.getJsonPath(), host.getOauthScopes())); - } else if (!Strings.isNullOrEmpty(host.getToken())) { - BearerTokenHeaderSupplier supplier = new BearerTokenHeaderSupplier(); - supplier.token = host.getToken(); - suppliers.add(supplier); - } - } - - @Override - public void intercept(RequestFacade request) { - if (suppliers != null && !suppliers.isEmpty()) { - request.addHeader("Authorization", SUPPLIERS_JOINER.join(suppliers)); - } - } - - public List getSuppliers() { - return suppliers; - } - - public void setSuppliers(List suppliers) { - this.suppliers = suppliers; - } - - private List suppliers = - new ArrayList(); - - /** TODO(rz): Good candidate for plugins. */ - public interface AuthorizationHeaderSupplier { - /** - * Returns the value to be added as the value in the "Authorization" HTTP header. - * - * @return - */ - String toString(); - } - - public static class BasicAuthHeaderSupplier implements AuthorizationHeaderSupplier { - public BasicAuthHeaderSupplier(String username, String password) { - this.username = username; - this.password = password; - } - - public String toString() { - return Credentials.basic(username, password); - } - - private final String username; - private final String password; - } - - @Slf4j - public static class GoogleBearerTokenHeaderSupplier implements AuthorizationHeaderSupplier { - @SneakyThrows - public GoogleBearerTokenHeaderSupplier(String jsonPath, List scopes) { - credentials = - GoogleCredentials.fromStream(new FileInputStream(new File(jsonPath))) - .createScoped(scopes); - } - - @SneakyThrows - public String toString() { - log.debug("Including Google Bearer token in Authorization header"); - credentials.refreshIfExpired(); - return credentials.getAccessToken().getTokenValue(); - } - - private GoogleCredentials credentials; - } - - @Slf4j - public static class BearerTokenHeaderSupplier implements AuthorizationHeaderSupplier { - public String toString() { - log.debug("Including raw bearer token in Authorization header"); - return "Bearer " + token; - } - - private Object token; - } -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/history/model/JenkinsBuildContent.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/history/model/JenkinsBuildContent.java deleted file mode 100644 index ea0873d42..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/history/model/JenkinsBuildContent.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.netflix.spinnaker.igor.history.model; - -import com.netflix.spinnaker.igor.jenkins.client.model.Project; -import lombok.AllArgsConstructor; -import lombok.Data; - -/** - * Encapsulates a build content block - * - *

- */ -@Data -@AllArgsConstructor -public class JenkinsBuildContent implements BuildContent { - private Project project; - private String master; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitor.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitor.java deleted file mode 100644 index 9927e43da..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitor.java +++ /dev/null @@ -1,314 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins; - -import static net.logstash.logback.argument.StructuredArguments.kv; - -import com.netflix.discovery.DiscoveryClient; -import com.netflix.spectator.api.Registry; -import com.netflix.spinnaker.igor.IgorConfigurationProperties; -import com.netflix.spinnaker.igor.config.JenkinsProperties; -import com.netflix.spinnaker.igor.history.EchoService; -import com.netflix.spinnaker.igor.history.model.JenkinsBuildContent; -import com.netflix.spinnaker.igor.history.model.JenkinsBuildEvent; -import com.netflix.spinnaker.igor.jenkins.client.model.Build; -import com.netflix.spinnaker.igor.jenkins.client.model.Project; -import com.netflix.spinnaker.igor.jenkins.client.model.ProjectsList; -import com.netflix.spinnaker.igor.polling.*; -import com.netflix.spinnaker.igor.service.BuildServiceProvider; -import com.netflix.spinnaker.igor.service.BuildServices; -import com.netflix.spinnaker.security.AuthenticatedRequest; -import java.time.Duration; -import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; -import retrofit.RetrofitError; - -/** Monitors new jenkins builds */ -@Slf4j -@Service -@SuppressWarnings("CatchException") -@ConditionalOnProperty("jenkins.enabled") -public class JenkinsBuildMonitor - extends CommonPollingMonitor< - JenkinsBuildMonitor.JobDelta, JenkinsBuildMonitor.JobPollingDelta> { - - private final JenkinsCache cache; - private final BuildServices buildServices; - private final boolean pollingEnabled; - private final Optional echoService; - private final JenkinsProperties jenkinsProperties; - - @Autowired - public JenkinsBuildMonitor( - IgorConfigurationProperties properties, - Registry registry, - Optional discoveryClient, - Optional lockService, - JenkinsCache cache, - BuildServices buildServices, - @Value("${jenkins.polling.enabled:true}") boolean pollingEnabled, - Optional echoService, - JenkinsProperties jenkinsProperties) { - super(properties, registry, discoveryClient, lockService); - this.cache = cache; - this.buildServices = buildServices; - this.pollingEnabled = pollingEnabled; - this.echoService = echoService; - this.jenkinsProperties = jenkinsProperties; - } - - @Override - public String getName() { - return "jenkinsBuildMonitor"; - } - - @Override - public boolean isInService() { - return pollingEnabled && super.isInService(); - } - - @Override - public void poll(final boolean sendEvents) { - buildServices - .getServiceNames(BuildServiceProvider.JENKINS) - .forEach(master -> pollSingle(new PollContext(master, !sendEvents))); - } - - /** - * Gets a list of jobs for this master & processes builds between last poll stamp and a sliding - * upper bound stamp, the cursor will be used to advanced to the upper bound when all builds are - * completed in the commit phase. - */ - @Override - protected JobPollingDelta generateDelta(PollContext ctx) { - final String master = ctx.partitionName; - log.trace("Checking for new builds in '{}'", master); - - final List delta = new ArrayList<>(); - registry - .timer("pollingMonitor.jenkins.retrieveProjects", "partition", master) - .record( - () -> { - JenkinsService jenkinsService = (JenkinsService) buildServices.getService(master); - ProjectsList projects = jenkinsService.getProjects(); - if (projects == null) { - return; - } - projects - .getList() - .forEach( - project -> { - processBuildsOfProject(jenkinsService, master, project, delta); - }); - }); - - return new JobPollingDelta(master, delta); - } - - private void processBuildsOfProject( - JenkinsService jenkinsService, - final String master, - final Project job, - List deltas) { - if (job.getLastBuild() == null) { - log.trace( - "[{}:{}] has no builds skipping...", kv("master", master), kv("job", job.getName())); - return; - } - - try { - Long cursor = cache.getLastPollCycleTimestamp(master, job.getName()); - Long lastBuildStamp = Long.valueOf(job.getLastBuild().getTimestamp()); - Date upperBound = new Date(lastBuildStamp); - if (Objects.equals(cursor, lastBuildStamp)) { - log.trace("[{}:{}] is up to date. skipping", master, job); - return; - } - - if (cursorIsUnset(cursor) - && !igorProperties.getSpinnaker().getBuild().isHandleFirstBuilds()) { - cache.setLastPollCycleTimestamp(master, job.getName(), lastBuildStamp); - return; - } - - List allBuilds = getBuilds(jenkinsService, master, job, cursor, lastBuildStamp); - List currentlyBuilding = - allBuilds.stream().filter(Build::isBuilding).collect(Collectors.toList()); - List completedBuilds = - allBuilds.stream().filter(b -> !b.isBuilding()).collect(Collectors.toList()); - - cursor = cursorIsUnset(cursor) ? lastBuildStamp : cursor; - Date lowerBound = new Date(cursor); - - // TODO(jc): Document. - if (!igorProperties.getSpinnaker().getBuild().isProcessBuildsOlderThanLookBackWindow()) { - completedBuilds = onlyInLookBackWindow(completedBuilds); - } - - JobDelta delta = new JobDelta(); - delta.setCursor(cursor); - delta.setName(job.getName()); - delta.setLastBuildStamp(lastBuildStamp); - delta.setUpperBound(upperBound); - delta.setLowerBound(lowerBound); - delta.setCompletedBuilds(completedBuilds); - delta.setRunningBuilds(currentlyBuilding); - - deltas.add(delta); - } catch (Exception e) { - log.error( - "Error processing builds for [{}:{}]", kv("master", master), kv("job", job.getName()), e); - if (e.getCause() instanceof RetrofitError) { - RetrofitError re = (RetrofitError) e.getCause(); - log.error( - "Error communicating with jenkins for [{}:{}]: {}", - kv("master", master), - kv("job", job.getName()), - kv("url", re.getUrl()), - re); - } - } - } - - private List getBuilds( - JenkinsService jenkinsService, - final String master, - final Project job, - final Long cursor, - final Long lastBuildStamp) { - if (cursorIsUnset(cursor)) { - log.debug("[{}:{}] setting new cursor to {}", master, job.getName(), lastBuildStamp); - final List builds = jenkinsService.getBuilds(job.getName()); - return Optional.ofNullable(builds).orElse(Collections.emptyList()); - } - - // filter between last poll and jenkins last build included - final List builds = - Optional.ofNullable(jenkinsService.getBuilds(job.getName())) - .orElse(Collections.emptyList()); - - return builds.stream() - .filter( - build -> { - String buildTimestamp = build.getTimestamp(); - long buildStamp = Long.parseLong(buildTimestamp == null ? "0" : buildTimestamp); - return buildStamp <= lastBuildStamp && buildStamp > cursor; - }) - .collect(Collectors.toList()); - } - - private List onlyInLookBackWindow(final List builds) { - Duration offsetSeconds = Duration.ofSeconds(getPollInterval()); - Duration lookBackWindowMinutes = - Duration.ofMinutes(igorProperties.getSpinnaker().getBuild().getLookBackWindowMins()); - - Instant lookBackDate = Instant.now().minus(offsetSeconds.plus(lookBackWindowMinutes)); - - return builds.stream() - .filter( - build -> { - Instant buildEndDate = - Instant.ofEpochMilli(Long.parseLong(build.getTimestamp())) - .plusMillis(build.getDuration()); - return buildEndDate.isAfter(lookBackDate); - }) - .collect(Collectors.toList()); - } - - @Override - protected void commitDelta(JobPollingDelta delta, final boolean sendEvents) { - final String master = delta.getMaster(); - - delta - .getItems() - .forEach( - job -> { - // post events for finished builds - job.completedBuilds.forEach( - build -> { - boolean eventPosted = - cache.getEventPosted(master, job.name, job.cursor, build.getNumber()); - if (!eventPosted && sendEvents) { - Project project = new Project(); - project.setName(job.getName()); - project.setLastBuild(build); - postEvent(project, master); - log.debug( - "[{}:{}]:{} event posted", master, job.getName(), build.getNumber()); - cache.setEventPosted( - master, job.getName(), job.getCursor(), build.getNumber()); - } - }); - - // advance cursor when all builds have completed in the interval - if (job.getRunningBuilds().isEmpty()) { - log.info( - "[{}:{}] has no other builds between [{} - {}], advancing cursor to {}", - kv("master", master), - kv("job", job.name), - job.getLowerBound(), - job.getUpperBound(), - job.getLastBuildStamp()); - cache.pruneOldMarkers(master, job.getName(), job.getCursor()); - cache.setLastPollCycleTimestamp(master, job.getName(), job.getLastBuildStamp()); - } - }); - } - - @Override - protected Integer getPartitionUpperThreshold(final String partition) { - return jenkinsProperties.getMasters().stream() - .filter(it -> it.getName().equals(partition)) - .findFirst() - .map(JenkinsProperties.JenkinsHost::getItemUpperThreshold) - .orElse(null); - } - - private void postEvent(final Project project, final String master) { - if (!echoService.isPresent()) { - log.warn("Cannot send build notification: Echo is not configured"); - registry - .counter(missedNotificationId.withTag("monitor", getClass().getSimpleName())) - .increment(); - return; - } - - AuthenticatedRequest.allowAnonymous( - () -> { - echoService.ifPresent( - echo -> - echo.postEvent(new JenkinsBuildEvent(new JenkinsBuildContent(project, master)))); - // TODO(rz): Add allowAnonymous(Runnable) - return null; - }); - } - - /** TODO(jc): First pass, not sure what this cursor actually is. Document. */ - private boolean cursorIsUnset(Long cursor) { - return (cursor == null || cursor == 0); - } - - @Data - @AllArgsConstructor - static class JobPollingDelta implements PollingDelta { - private String master; - private List items; - } - - @Data - static class JobDelta implements DeltaItem { - private Long cursor; - private String name; - private Long lastBuildStamp; - private Date lowerBound; - private Date upperBound; - private List completedBuilds; - private List runningBuilds; - } -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/JenkinsClient.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/JenkinsClient.java deleted file mode 100644 index de8a8395b..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/JenkinsClient.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.netflix.spinnaker.igor.jenkins.client; - -import com.netflix.spinnaker.igor.jenkins.client.model.*; -import java.util.Map; -import retrofit.client.Response; -import retrofit.http.*; - -/** Interface for interacting with a Jenkins Service via Xml */ -@SuppressWarnings("LineLength") -public interface JenkinsClient { - @GET( - "/api/xml?tree=jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url]]]]]]]]]]]&exclude=/*/*/*/action[not(totalCount)]") - public abstract ProjectsList getProjects(); - - @GET( - "/api/xml?tree=jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name]]]]]]]]]]") - public abstract JobList getJobs(); - - @GET( - "/job/{jobName}/api/xml?exclude=/*/build/action[not(totalCount)]&tree=builds[number,url,duration,timestamp,result,building,url,fullDisplayName,actions[failCount,skipCount,totalCount]]") - public abstract BuildsList getBuilds(@EncodedPath("jobName") String jobName); - - @GET( - "/job/{jobName}/api/xml?tree=name,url,actions[processes[name]],downstreamProjects[name,url],upstreamProjects[name,url]") - public abstract BuildDependencies getDependencies(@EncodedPath("jobName") String jobName); - - @GET( - "/job/{jobName}/{buildNumber}/api/xml?exclude=/*/action[not(totalCount)]&tree=actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url,fullDisplayName,artifacts[displayPath,fileName,relativePath]") - public abstract Build getBuild( - @EncodedPath("jobName") String jobName, @Path("buildNumber") Integer buildNumber); - - @GET( - "/job/{jobName}/{buildNumber}/api/xml?exclude=/*/action[not(build|lastBuiltRevision)]&tree=actions[remoteUrls,lastBuiltRevision[branch[name,SHA1]],build[revision[branch[name,SHA1]]]]") - public abstract ScmDetails getGitDetails( - @EncodedPath("jobName") String jobName, @Path("buildNumber") Integer buildNumber); - - @GET("/job/{jobName}/lastCompletedBuild/api/xml") - public abstract Build getLatestBuild(@EncodedPath("jobName") String jobName); - - @GET("/queue/item/{itemNumber}/api/xml") - public abstract QueuedJob getQueuedItem(@Path("itemNumber") Integer item); - - @POST("/job/{jobName}/build") - public abstract Response build( - @EncodedPath("jobName") String jobName, - @Body String emptyRequest, - @Header("Jenkins-Crumb") String crumb); - - @POST("/job/{jobName}/buildWithParameters") - public abstract Response buildWithParameters( - @EncodedPath("jobName") String jobName, - @QueryMap Map queryParams, - @Body String EmptyRequest, - @Header("Jenkins-Crumb") String crumb); - - @POST("/job/{jobName}/{buildNumber}/stop") - public abstract Response stopRunningBuild( - @EncodedPath("jobName") String jobName, - @Path("buildNumber") Integer buildNumber, - @Body String EmptyRequest, - @Header("Jenkins-Crumb") String crumb); - - @POST("/queue/cancelItem") - public abstract Response stopQueuedBuild( - @Query("id") String queuedBuild, - @Body String emptyRequest, - @Header("Jenkins-Crumb") String crumb); - - @GET( - "/job/{jobName}/api/xml?exclude=/*/action&exclude=/*/build&exclude=/*/property[not(parameterDefinition)]") - public abstract JobConfig getJobConfig(@EncodedPath("jobName") String jobName); - - @Streaming - @GET("/job/{jobName}/{buildNumber}/artifact/{fileName}") - public abstract Response getPropertyFile( - @EncodedPath("jobName") String jobName, - @Path("buildNumber") Integer buildNumber, - @Path(value = "fileName", encode = false) String fileName); - - @GET("/crumbIssuer/api/xml") - public abstract Crumb getCrumb(); -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/JenkinsMasters.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/JenkinsMasters.java deleted file mode 100644 index 9d2b6adc6..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/JenkinsMasters.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client; - -import com.netflix.spinnaker.igor.jenkins.JenkinsService; -import java.util.Map; - -/** Wrapper class for a collection of jenkins clients */ -public class JenkinsMasters { - public Map getMap() { - return map; - } - - public void setMap(Map map) { - this.map = map; - } - - private Map map; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Action.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Action.java deleted file mode 100644 index a303b18dc..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Action.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import javax.xml.bind.annotation.XmlElement; - -public class Action { - public Revision getLastBuiltRevision() { - return lastBuiltRevision; - } - - public void setLastBuiltRevision(Revision lastBuiltRevision) { - this.lastBuiltRevision = lastBuiltRevision; - } - - public ScmBuild getBuild() { - return build; - } - - public void setBuild(ScmBuild build) { - this.build = build; - } - - public String getRemoteUrl() { - return remoteUrl; - } - - public void setRemoteUrl(String remoteUrl) { - this.remoteUrl = remoteUrl; - } - - @XmlElement(required = false) - private Revision lastBuiltRevision; - - @XmlElement(required = false) - private ScmBuild build; - - @XmlElement(required = false) - private String remoteUrl; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Branch.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Branch.java deleted file mode 100644 index 8ad3725d4..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Branch.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import javax.xml.bind.annotation.XmlElement; - -public class Branch { - - @XmlElement(required = false) - private String name; - - @XmlElement(required = false, name = "SHA1") - private String sha1; - - /** - * Given a full branch reference (e.g. {@code origin/master}), this method will return the branch - * name without the repository (e.g. {@code master}). - */ - public String getSimpleBranchName() { - if (name == null) { - return null; - } - String[] segments = name.split("/"); - return segments[segments.length - 1]; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSha1() { - return sha1; - } - - public void setSha1(String sha1) { - this.sha1 = sha1; - } -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Build.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Build.java deleted file mode 100644 index d3358b787..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Build.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.netflix.spinnaker.igor.build.model.GenericArtifact; -import com.netflix.spinnaker.igor.build.model.GenericBuild; -import com.netflix.spinnaker.igor.build.model.Result; -import java.util.List; -import java.util.stream.Collectors; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** Represents a build in Jenkins */ -@JsonInclude(JsonInclude.Include.NON_NULL) -@XmlRootElement -public class Build { - public GenericBuild genericBuild(final String jobName) { - GenericBuild.GenericBuildBuilder builder = - GenericBuild.builder() - .building(building) - .number(number) - .duration(duration.intValue()) - // TODO(rz): Groovyism. What does Groovy do when `result` is null and you cast it to an - // enum? WHO KNOWS, but - // all of the igor tests depend on _something_ being set from null. - .result((result == null) ? Result.NOT_BUILT : Result.valueOf(result)) - .name(jobName) - .url(url) - .timestamp(timestamp) - .fullDisplayName(fullDisplayName); - - if (artifacts != null && !artifacts.isEmpty()) { - builder.artifacts( - artifacts.stream() - .map( - buildArtifact -> { - GenericArtifact artifact = buildArtifact.getGenericArtifact(); - artifact.setName(jobName); - artifact.setVersion(getNumber().toString()); - return artifact; - }) - .collect(Collectors.toList())); - } - - if (testResults != null && !testResults.isEmpty()) { - builder.testResults(testResults); - } - - return builder.build(); - } - - public boolean getBuilding() { - return building; - } - - public boolean isBuilding() { - return building; - } - - public void setBuilding(boolean building) { - this.building = building; - } - - public Integer getNumber() { - return number; - } - - public void setNumber(Integer number) { - this.number = number; - } - - public String getResult() { - return result; - } - - public void setResult(String result) { - this.result = result; - } - - public String getTimestamp() { - return timestamp; - } - - public void setTimestamp(String timestamp) { - this.timestamp = timestamp; - } - - public Long getDuration() { - return duration; - } - - public void setDuration(Long duration) { - this.duration = duration; - } - - public Integer getEstimatedDuration() { - return estimatedDuration; - } - - public void setEstimatedDuration(Integer estimatedDuration) { - this.estimatedDuration = estimatedDuration; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getBuiltOn() { - return builtOn; - } - - public void setBuiltOn(String builtOn) { - this.builtOn = builtOn; - } - - public String getFullDisplayName() { - return fullDisplayName; - } - - public void setFullDisplayName(String fullDisplayName) { - this.fullDisplayName = fullDisplayName; - } - - public List getArtifacts() { - return artifacts; - } - - public void setArtifacts(List artifacts) { - this.artifacts = artifacts; - } - - public List getTestResults() { - return testResults; - } - - public void setTestResults(List testResults) { - this.testResults = testResults; - } - - private boolean building; - private Integer number; - - @XmlElement(required = false) - private String result; - - private String timestamp; - - @XmlElement(required = false) - private Long duration; - - @XmlElement(required = false) - private Integer estimatedDuration; - - @XmlElement(required = false) - private String id; - - private String url; - - @XmlElement(required = false) - private String builtOn; - - @XmlElement(required = false) - private String fullDisplayName; - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "artifact", required = false) - private List artifacts; - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "action", required = false) - private List testResults; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildArtifact.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildArtifact.java deleted file mode 100644 index f19926c24..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildArtifact.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.netflix.spinnaker.igor.build.model.GenericArtifact; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** Represents a build artifact */ -@XmlRootElement(name = "artifact") -public class BuildArtifact { - public GenericArtifact getGenericArtifact() { - GenericArtifact artifact = new GenericArtifact(fileName, displayPath, relativePath); - artifact.setType("jenkins/file"); - artifact.setReference(relativePath); - return artifact; - } - - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public String getDisplayPath() { - return displayPath; - } - - public void setDisplayPath(String displayPath) { - this.displayPath = displayPath; - } - - public String getRelativePath() { - return relativePath; - } - - public void setRelativePath(String relativePath) { - this.relativePath = relativePath; - } - - @XmlElement(required = false) - private String fileName; - - @XmlElement(required = false) - private String displayPath; - - @XmlElement(required = false) - private String relativePath; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependencies.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependencies.java deleted file mode 100644 index 921b5a564..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependencies.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** Captures build dependencies for a jenkins job */ -@XmlRootElement -public class BuildDependencies { - public List getDownstreamProjects() { - return downstreamProjects; - } - - public void setDownstreamProjects(List downstreamProjects) { - this.downstreamProjects = downstreamProjects; - } - - public List getUpstreamProjects() { - return upstreamProjects; - } - - public void setUpstreamProjects(List upstreamProjects) { - this.upstreamProjects = upstreamProjects; - } - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "downstreamProject", required = false) - private List downstreamProjects; - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "upstreamProject", required = false) - private List upstreamProjects; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependency.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependency.java deleted file mode 100644 index 57db4a4f2..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependency.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import org.simpleframework.xml.Default; - -/** Represents either an upstream or downstream dependency in Jenkins */ -@Default -public class BuildDependency { - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - private String name; - private String url; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildsList.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildsList.java deleted file mode 100644 index 2a7119b3a..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/BuildsList.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** Represents a list of builds */ -@XmlRootElement -public class BuildsList { - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "build") - private List list; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Crumb.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Crumb.java deleted file mode 100644 index f9d1e3da9..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Crumb.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** Represents a Jenkins CSRF Crumb. */ -@XmlRootElement -public class Crumb { - public String getCrumbRequestField() { - return crumbRequestField; - } - - public void setCrumbRequestField(String crumbRequestField) { - this.crumbRequestField = crumbRequestField; - } - - public String getCrumb() { - return crumb; - } - - public void setCrumb(String crumb) { - this.crumb = crumb; - } - - @XmlElement private String crumbRequestField; - @XmlElement private String crumb; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/DownstreamProject.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/DownstreamProject.java deleted file mode 100644 index 80b52f913..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/DownstreamProject.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import org.simpleframework.xml.Root; - -/** Represents a Jenkins job downstream project */ -@Root(name = "downstreamProject", strict = false) -public class DownstreamProject extends RelatedProject {} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Job.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Job.java deleted file mode 100644 index dec6fe205..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Job.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; - -public class Job { - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "job") - private List list; - - @XmlElement(required = false) - private String name; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/JobConfig.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/JobConfig.java deleted file mode 100644 index f266a9f73..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/JobConfig.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.netflix.spinnaker.igor.build.model.JobConfiguration; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementWrapper; -import javax.xml.bind.annotation.XmlRootElement; - -/** Represents the basic Jenkins job configuration information */ -@XmlRootElement -public class JobConfig implements JobConfiguration { - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean getBuildable() { - return buildable; - } - - public boolean isBuildable() { - return buildable; - } - - public void setBuildable(boolean buildable) { - this.buildable = buildable; - } - - public String getColor() { - return color; - } - - public void setColor(String color) { - this.color = color; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public List getParameterDefinitionList() { - return parameterDefinitionList; - } - - public void setParameterDefinitionList(List parameterDefinitionList) { - this.parameterDefinitionList = parameterDefinitionList; - } - - public List getUpstreamProjectList() { - return upstreamProjectList; - } - - public void setUpstreamProjectList(List upstreamProjectList) { - this.upstreamProjectList = upstreamProjectList; - } - - public List getDownstreamProjectList() { - return downstreamProjectList; - } - - public void setDownstreamProjectList(List downstreamProjectList) { - this.downstreamProjectList = downstreamProjectList; - } - - public boolean getConcurrentBuild() { - return concurrentBuild; - } - - public boolean isConcurrentBuild() { - return concurrentBuild; - } - - public void setConcurrentBuild(boolean concurrentBuild) { - this.concurrentBuild = concurrentBuild; - } - - @XmlElement(required = false) - private String description; - - @XmlElement private String displayName; - @XmlElement private String name; - @XmlElement private boolean buildable; - @XmlElement private String color; - @XmlElement private String url; - - @XmlElementWrapper(name = "property") - @XmlElement(name = "parameterDefinition", required = false) - private List parameterDefinitionList; - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "upstreamProject", required = false) - private List upstreamProjectList; - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "downstreamProject", required = false) - private List downstreamProjectList; - - @XmlElement private boolean concurrentBuild; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/JobList.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/JobList.java deleted file mode 100644 index 3f48473a3..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/JobList.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** Represents a list of projects */ -@XmlRootElement(name = "hudson") -public class JobList { - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "job") - private List list; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Project.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Project.java deleted file mode 100644 index b9d755a92..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Project.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; - -/** Represents a Project returned by the Jenkins service in the project list */ -public class Project { - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Build getLastBuild() { - return lastBuild; - } - - public void setLastBuild(Build lastBuild) { - this.lastBuild = lastBuild; - } - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "job", required = false) - private List list; - - @XmlElement private String name; - @XmlElement private Build lastBuild; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ProjectsList.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ProjectsList.java deleted file mode 100644 index 0e4845a28..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ProjectsList.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** Represents a list of projects */ -@XmlRootElement(name = "hudson") -public class ProjectsList { - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "job") - private List list; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/QueuedJob.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/QueuedJob.java deleted file mode 100644 index 0d7bbb7de..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/QueuedJob.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement -public class QueuedJob { - @XmlElement(name = "number") - public Integer getNumber() { - final QueuedExecutable executable1 = executable; - return (executable1 == null ? null : executable1.getNumber()); - } - - public QueuedExecutable getExecutable() { - return executable; - } - - public void setExecutable(QueuedExecutable executable) { - this.executable = executable; - } - - @XmlElement private QueuedExecutable executable; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/RelatedProject.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/RelatedProject.java deleted file mode 100644 index c99edd5f9..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/RelatedProject.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Represents a upstream/downstream project for a Jenkins job */ -@Root(strict = false) -public class RelatedProject { - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getColor() { - return color; - } - - public void setColor(String color) { - this.color = color; - } - - @Element private String name; - @Element private String url; - @Element private String color; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ScmDetails.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ScmDetails.java deleted file mode 100644 index 8fd9c50ae..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ScmDetails.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.netflix.spinnaker.igor.build.model.GenericGitRevision; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import javax.xml.bind.annotation.XmlElement; - -/** - * Represents git details - * - *

The serialization of these details in the Jenkins build XML changed in version 4.0.0 of the - * jenkins-git plugin. - * - *

Prior to 4.0.0, the format was: - * 943a702d06f34599aee1f8da8ef9f7296031d699 - * refs/remotes/origin/master - * some-url - * - *

As of version 4.0.0, the format is: - * 943a702d06f34599aee1f8da8ef9f7296031d699 - * refs/remotes/origin/master some-url - * - * - *

The code in this module should remain compatible with both formats to ensure that SCM info is - * populated in Spinnaker regardless of which version of the jenkins-git plugin is being used. - */ -public class ScmDetails { - /** TODO(rz): Rename to gitRevisions */ - public List genericGitRevisions() { - List genericGitRevisions = new ArrayList<>(); - - if (actions == null) { - return null; - } - - for (Action action : actions) { - final Revision lastBuiltRevision = (action == null ? null : action.getLastBuiltRevision()); - final ScmBuild build = (action == null ? null : action.getBuild()); - final Revision revision = - (lastBuiltRevision == null) - ? (build == null ? null : build.getRevision()) - : lastBuiltRevision; - final List branch = (revision == null ? null : revision.getBranch()); - - if (branch != null && !branch.isEmpty()) { - genericGitRevisions.addAll( - branch.stream() - .map( - b -> - GenericGitRevision.builder() - .name(b.getName()) - .branch(b.getSimpleBranchName()) - .sha1(b.getSha1()) - .remoteUrl(action.getRemoteUrl()) - .build()) - .collect(Collectors.toList())); - } - } - - // If the same revision appears in both the old and new locations in the XML, we only want to - // return it once. - return genericGitRevisions.stream().distinct().collect(Collectors.toList()); - } - - public ArrayList getActions() { - return actions; - } - - public void setActions(ArrayList actions) { - this.actions = actions; - } - - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "action") - private ArrayList actions; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/TestResults.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/TestResults.java deleted file mode 100644 index 390629606..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/TestResults.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -import com.netflix.spinnaker.igor.build.model.TestResult; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -/** Represents a build artifact */ -@Root(name = "action", strict = false) -public class TestResults implements TestResult { - public int getFailCount() { - return failCount; - } - - public void setFailCount(int failCount) { - this.failCount = failCount; - } - - public int getSkipCount() { - return skipCount; - } - - public void setSkipCount(int skipCount) { - this.skipCount = skipCount; - } - - public int getTotalCount() { - return totalCount; - } - - public void setTotalCount(int totalCount) { - this.totalCount = totalCount; - } - - public String getUrlName() { - return urlName; - } - - public void setUrlName(String urlName) { - this.urlName = urlName; - } - - @Element(required = false) - private int failCount; - - @Element(required = false) - private int skipCount; - - @Element(required = false) - private int totalCount; - - @Element(required = false) - private String urlName; -} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/UpstreamProject.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/UpstreamProject.java deleted file mode 100644 index e8bf377a6..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/UpstreamProject.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.netflix.spinnaker.igor.jenkins.client.model; - -/** Represents a Jenkins job upstream project */ -public class UpstreamProject extends RelatedProject {} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/exceptions/InvalidJobParameterException.java b/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/exceptions/InvalidJobParameterException.java deleted file mode 100644 index 04797182a..000000000 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/exceptions/InvalidJobParameterException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.jenkins.exceptions; - -import com.netflix.spinnaker.kork.web.exceptions.InvalidRequestException; - -public class InvalidJobParameterException extends InvalidRequestException { - public InvalidJobParameterException(String message) { - super(message); - } -} diff --git a/igor-web/igor-web.gradle b/igor-web/igor-web.gradle index b95f6a4b9..25e0371b3 100644 --- a/igor-web/igor-web.gradle +++ b/igor-web/igor-web.gradle @@ -18,8 +18,8 @@ test { dependencies { implementation project(":igor-core") implementation project(":igor-monitor-artifactory") - implementation project(":igor-monitor-jenkins") + implementation platform("com.netflix.spinnaker.kork:kork-bom:$korkVersion") compileOnly "org.projectlombok:lombok" annotationProcessor platform("com.netflix.spinnaker.kork:kork-bom:$korkVersion") annotationProcessor "org.projectlombok:lombok" diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractor.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractor.java index 85c31e975..f11d0155c 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractor.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractor.java @@ -31,7 +31,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; -/** TODO(rz): Cannot move to igor-core due to Jenkins dependency */ +/** TODO(rz): Cannot move to kork-core due to Jenkins dependency */ @Component @RequiredArgsConstructor @Slf4j diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/BuildController.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/BuildController.groovy index 01981ef48..38ddba247 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/BuildController.groovy +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/BuildController.groovy @@ -19,13 +19,19 @@ package com.netflix.spinnaker.igor.build import com.netflix.spinnaker.igor.artifacts.ArtifactExtractor import com.netflix.spinnaker.igor.build.model.GenericBuild +import com.netflix.spinnaker.igor.exceptions.BuildJobError +import com.netflix.spinnaker.igor.exceptions.QueuedJobDeterminationError +import com.netflix.spinnaker.igor.jenkins.client.model.JobConfig +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService import com.netflix.spinnaker.igor.service.ArtifactDecorator import com.netflix.spinnaker.igor.service.BuildOperations import com.netflix.spinnaker.igor.service.BuildProperties import com.netflix.spinnaker.igor.service.BuildServices -import com.netflix.spinnaker.igor.service.BuildQueueOperations +import com.netflix.spinnaker.igor.travis.service.TravisService import com.netflix.spinnaker.kork.artifacts.model.Artifact +import com.netflix.spinnaker.kork.web.exceptions.InvalidRequestException import com.netflix.spinnaker.kork.web.exceptions.NotFoundException +import groovy.transform.InheritConstructors import groovy.util.logging.Slf4j import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.PathVariable @@ -112,8 +118,12 @@ class BuildController { @PreAuthorize("hasPermission(#master, 'BUILD_SERVICE', 'READ')") Object getQueueLocation(@PathVariable String master, @PathVariable int item) { def buildService = getBuildService(master) - if (buildService instanceof BuildQueueOperations) { - return buildService.getQueuedBuild(String.valueOf(item)); + if (buildService instanceof JenkinsService) { + JenkinsService jenkinsService = (JenkinsService) buildService + return jenkinsService.queuedBuild(item) + } else if (buildService instanceof TravisService) { + TravisService travisService = (TravisService) buildService + return travisService.queuedBuild(item) } throw new UnsupportedOperationException(String.format("Queued builds are not supported for build service %s", master)) } @@ -136,11 +146,28 @@ class BuildController { @PathVariable Integer buildNumber) { def buildService = getBuildService(master) - if (buildService instanceof BuildQueueOperations) { - buildService.stopQueuedBuild(jobName, queuedBuild, buildNumber) + if (buildService instanceof JenkinsService) { + // Jobs that haven't been started yet won't have a buildNumber + // (They're still in the queue). We use 0 to denote that case + if (buildNumber != 0 && + buildService.metaClass.respondsTo(buildService, 'stopRunningBuild')) { + buildService.stopRunningBuild(jobName, buildNumber) + } + + // The jenkins api for removing a job from the queue (http:///queue/cancelItem?id=) + // always returns a 404. This try catch block insures that the exception is eaten instead + // of being handled by the handleOtherException handler and returning a 500 to orca + try { + if (buildService.metaClass.respondsTo(buildService, 'stopQueuedBuild')) { + buildService.stopQueuedBuild(queuedBuild) + } + } catch (RetrofitError e) { + if (e.response?.status != NOT_FOUND.value()) { + throw e + } + } } - // TODO(rz): lol, for real? "true" } @@ -151,7 +178,58 @@ class BuildController { @RequestParam Map requestParams, HttpServletRequest request) { def job = ((String) request.getAttribute( HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).split('/').drop(4).join('/') - return getBuildService(master).triggerBuildWithParameters(job, requestParams) + def buildService = getBuildService(master) + if (buildService instanceof JenkinsService) { + def response + JenkinsService jenkinsService = (JenkinsService) buildService + JobConfig jobConfig = jenkinsService.getJobConfig(job) + if (!jobConfig.buildable) { + throw new BuildJobError("Job '${job}' is not buildable. It may be disabled.") + } + + if (jobConfig.parameterDefinitionList?.size() > 0) { + validateJobParameters(jobConfig, requestParams) + } + if (requestParams && jobConfig.parameterDefinitionList?.size() > 0) { + response = jenkinsService.buildWithParameters(job, requestParams) + } else if (!requestParams && jobConfig.parameterDefinitionList?.size() > 0) { + // account for when you just want to fire a job with the default parameter values by adding a dummy param + response = jenkinsService.buildWithParameters(job, ['startedBy': "igor"]) + } else if (!requestParams && (!jobConfig.parameterDefinitionList || jobConfig.parameterDefinitionList.size() == 0)) { + response = jenkinsService.build(job) + } else { // Jenkins will reject the build, so don't even try + // we should throw a BuildJobError, but I get a bytecode error : java.lang.VerifyError: Bad method call from inside of a branch + throw new RuntimeException("job : ${job}, passing params to a job which doesn't need them") + } + + if (response.status != 201) { + throw new BuildJobError("Received a non-201 status when submitting job '${job}' to master '${master}'") + } + + log.info("Submitted build job '{}'", kv("job", job)) + def locationHeader = response.headers.find { it.name.toLowerCase() == "location" } + if (!locationHeader) { + throw new QueuedJobDeterminationError("Could not find Location header for job '${job}'") + } + def queuedLocation = locationHeader.value + + queuedLocation.split('/')[-1] + } else { + return buildService.triggerBuildWithParameters(job, requestParams) + } + } + + static void validateJobParameters(JobConfig jobConfig, Map requestParams) { + jobConfig.parameterDefinitionList.each { parameterDefinition -> + String matchingParam = requestParams[parameterDefinition.name] + if (matchingParam != null && + parameterDefinition.type == 'ChoiceParameterDefinition' && + parameterDefinition.choices != null && + !parameterDefinition.choices.contains(matchingParam)) { + throw new InvalidJobParameterException("`${matchingParam}` is not a valid choice " + + "for `${parameterDefinition.name}`. Valid choices are: ${parameterDefinition.choices.join(', ')}") + } + } } @RequestMapping(value = '/builds/properties/{buildNumber}/{fileName}/{master:.+}/**') @@ -178,4 +256,8 @@ class BuildController { } return buildService } + + @InheritConstructors + static class InvalidJobParameterException extends InvalidRequestException {} + } diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy index 9bab48938..9e9f2ddd7 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/InfoController.groovy @@ -18,10 +18,10 @@ package com.netflix.spinnaker.igor.build import com.netflix.spinnaker.igor.config.GoogleCloudBuildProperties -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.service.BuildService import com.netflix.spinnaker.igor.service.BuildServices -import com.netflix.spinnaker.igor.service.JobNamesProvider import com.netflix.spinnaker.igor.wercker.WerckerService import com.netflix.spinnaker.kork.web.exceptions.NotFoundException import groovy.util.logging.Slf4j @@ -38,7 +38,7 @@ import org.springframework.web.servlet.HandlerMapping import javax.servlet.http.HttpServletRequest /** - * A controller that provides build service information + * A controller that provides jenkins information */ @RestController @Slf4j @@ -85,10 +85,31 @@ class InfoController { throw new NotFoundException("Master '${master}' does not exist") } - if (buildService instanceof JobNamesProvider) { - return buildService.getJobNames() + if (buildService instanceof JenkinsService) { + JenkinsService jenkinsService = (JenkinsService) buildService + def jobList = [] + def recursiveGetJobs + + recursiveGetJobs = { list, prefix = "" -> + if (prefix) { + prefix = prefix + "/job/" + } + list.each { + if (it.list == null || it.list.empty) { + jobList << prefix + it.name + } else { + recursiveGetJobs(it.list, prefix + it.name) + } + } + } + recursiveGetJobs(jenkinsService.jobs.list) + + return jobList + } else if (buildService instanceof WerckerService) { + WerckerService werckerService = (WerckerService) buildService + return werckerService.getJobs() } else { - return buildCache.getJobNames(master) + return buildCache.getJobNames(master) } } diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/GenericBuild.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/model/GenericBuild.java similarity index 90% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/GenericBuild.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/model/GenericBuild.java index aa1a76dbb..0d62a6514 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/build/model/GenericBuild.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/model/GenericBuild.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2019 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.netflix.spinnaker.igor.jenkins.client.model.TestResults; import java.util.List; import java.util.Map; -import lombok.Builder; import lombok.Data; @Data -@Builder @JsonInclude(JsonInclude.Include.NON_NULL) public class GenericBuild { private boolean building; @@ -37,7 +36,7 @@ public class GenericBuild { private Result result; private List artifacts; - private List testResults; + private List testResults; private String url; private String id; diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/model/GenericProject.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/model/GenericProject.java index 654f5d483..ab3852c0a 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/model/GenericProject.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/build/model/GenericProject.java @@ -19,7 +19,6 @@ import lombok.AllArgsConstructor; import lombok.Data; -/** TODO(rz): Jenkins in wrong module. */ @Data @AllArgsConstructor public class GenericProject { diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/JenkinsConfig.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/JenkinsConfig.groovy new file mode 100644 index 000000000..39d485fb0 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/JenkinsConfig.groovy @@ -0,0 +1,210 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.config + +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.xml.XmlMapper +import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule +import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.fiat.model.resources.Permissions +import com.netflix.spinnaker.igor.IgorConfigurationProperties +import com.netflix.spinnaker.igor.config.client.DefaultJenkinsOkHttpClientProvider +import com.netflix.spinnaker.igor.config.client.DefaultJenkinsRetrofitRequestInterceptorProvider +import com.netflix.spinnaker.igor.config.client.JenkinsOkHttpClientProvider +import com.netflix.spinnaker.igor.config.client.JenkinsRetrofitRequestInterceptorProvider +import com.netflix.spinnaker.igor.jenkins.client.JenkinsClient +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService +import com.netflix.spinnaker.igor.service.BuildServices +import com.netflix.spinnaker.kork.telemetry.InstrumentedProxy +import com.squareup.okhttp.OkHttpClient +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import retrofit.Endpoints +import retrofit.RequestInterceptor +import retrofit.RestAdapter +import retrofit.client.OkClient +import retrofit.converter.JacksonConverter + +import javax.net.ssl.KeyManager +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager +import javax.validation.Valid +import java.security.KeyStore +import java.security.cert.CertificateException +import java.security.cert.X509Certificate +import java.util.concurrent.TimeUnit + +/** + * Converts the list of Jenkins Configuration properties a collection of clients to access the Jenkins hosts + */ +@Configuration +@Slf4j +@CompileStatic +@ConditionalOnProperty("jenkins.enabled") +@EnableConfigurationProperties(JenkinsProperties) +class JenkinsConfig { + + @Bean + @ConditionalOnMissingBean + JenkinsOkHttpClientProvider jenkinsOkHttpClientProvider() { + return new DefaultJenkinsOkHttpClientProvider() + } + + @Bean + @ConditionalOnMissingBean + JenkinsRetrofitRequestInterceptorProvider jenkinsRetrofitRequestInterceptorProvider() { + return new DefaultJenkinsRetrofitRequestInterceptorProvider() + } + + @Bean + Map jenkinsMasters(BuildServices buildServices, + IgorConfigurationProperties igorConfigurationProperties, + @Valid JenkinsProperties jenkinsProperties, + JenkinsOkHttpClientProvider jenkinsOkHttpClientProvider, + JenkinsRetrofitRequestInterceptorProvider jenkinsRetrofitRequestInterceptorProvider, + Registry registry) { + log.info "creating jenkinsMasters" + Map jenkinsMasters = jenkinsProperties?.masters?.collectEntries { JenkinsProperties.JenkinsHost host -> + log.info "bootstrapping ${host.address} as ${host.name}" + [(host.name): jenkinsService( + host.name, + (JenkinsClient) InstrumentedProxy.proxy( + registry, + jenkinsClient( + host, + jenkinsOkHttpClientProvider.provide(host), + jenkinsRetrofitRequestInterceptorProvider.provide(host), + igorConfigurationProperties.client.timeout + ), + "jenkinsClient", + [master: host.name]), + host.csrf, + host.permissions.build() + )] + } + + buildServices.addServices(jenkinsMasters) + jenkinsMasters + } + + static JenkinsService jenkinsService(String jenkinsHostId, JenkinsClient jenkinsClient, Boolean csrf, Permissions permissions) { + return new JenkinsService(jenkinsHostId, jenkinsClient, csrf, permissions) + } + + static ObjectMapper getObjectMapper() { + return new XmlMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .registerModule(new JaxbAnnotationModule()) + } + + static JenkinsClient jenkinsClient(JenkinsProperties.JenkinsHost host, + OkHttpClient client, + RequestInterceptor requestInterceptor, + int timeout = 30000) { + client.setReadTimeout(timeout, TimeUnit.MILLISECONDS) + + if (host.skipHostnameVerification) { + client.setHostnameVerifier({ hostname, _ -> + true + }) + } + + TrustManager[] trustManagers = null + KeyManager[] keyManagers = null + + if (host.trustStore) { + if (host.trustStore.equals("*")) { + trustManagers = [new TrustAllTrustManager()] + } else { + def trustStorePassword = host.trustStorePassword + def trustStore = KeyStore.getInstance(host.trustStoreType) + new File(host.trustStore).withInputStream { + trustStore.load(it, trustStorePassword.toCharArray()) + } + def trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(trustStore) + + trustManagers = trustManagerFactory.trustManagers + } + } + + if (host.keyStore) { + def keyStorePassword = host.keyStorePassword + def keyStore = KeyStore.getInstance(host.keyStoreType) + new File(host.keyStore).withInputStream { + keyStore.load(it, keyStorePassword.toCharArray()) + } + def keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()) + + keyManagers = keyManagerFactory.keyManagers + } + + if (trustManagers || keyManagers) { + def sslContext = SSLContext.getInstance("TLS") + sslContext.init(keyManagers, trustManagers, null) + + client.setSslSocketFactory(sslContext.socketFactory) + } + + new RestAdapter.Builder() + .setEndpoint(Endpoints.newFixedEndpoint(host.address)) + .setRequestInterceptor(new RequestInterceptor() { + @Override + void intercept(RequestInterceptor.RequestFacade request) { + request.addHeader("User-Agent", "Spinnaker-igor") + requestInterceptor.intercept(request) + } + }) + .setClient(new OkClient(client)) + .setConverter(new JacksonConverter(getObjectMapper())) + .build() + .create(JenkinsClient) + } + + static JenkinsClient jenkinsClient(JenkinsProperties.JenkinsHost host, int timeout = 30000) { + OkHttpClient client = new OkHttpClient() + jenkinsClient(host, client, RequestInterceptor.NONE, timeout) + } + + static class TrustAllTrustManager implements X509TrustManager { + @Override + void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + // do nothing + } + + @Override + void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + // do nothing + } + + @Override + X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0] + } + } + +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/JenkinsProperties.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/JenkinsProperties.groovy new file mode 100644 index 000000000..091e18823 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/JenkinsProperties.groovy @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.config + +import com.netflix.spinnaker.fiat.model.resources.Permissions +import groovy.transform.CompileStatic +import org.hibernate.validator.constraints.NotEmpty +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.validation.annotation.Validated + +import javax.validation.Valid +import java.security.KeyStore + +/** + * Helper class to map masters in properties file into a validated property map + */ +@CompileStatic +@ConfigurationProperties(prefix = 'jenkins') +@Validated +class JenkinsProperties implements BuildServerProperties { + @Valid + List masters + + static class JenkinsHost implements BuildServerProperties.Host { + @NotEmpty + String name + + @NotEmpty + String address + + String username + + String password + + Boolean csrf = false + + // These are needed for Google-based OAuth with a service account credential + String jsonPath + List oauthScopes = [] + + // Can be used directly, if available. + String token + + Integer itemUpperThreshold; + + String trustStore + String trustStoreType = KeyStore.getDefaultType() + String trustStorePassword + + String keyStore + String keyStoreType = KeyStore.getDefaultType() + String keyStorePassword + + Boolean skipHostnameVerification = false + + Permissions.Builder permissions = new Permissions.Builder() + } +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/auth/AuthRequestInterceptor.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/auth/AuthRequestInterceptor.groovy new file mode 100644 index 000000000..f1ca16b2a --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/auth/AuthRequestInterceptor.groovy @@ -0,0 +1,96 @@ +/* + * Copyright 2017 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.config.auth + +import com.google.auth.oauth2.GoogleCredentials +import com.netflix.spinnaker.igor.config.JenkinsProperties +import com.squareup.okhttp.Credentials +import groovy.util.logging.Slf4j +import retrofit.RequestInterceptor + +@Slf4j +class AuthRequestInterceptor implements RequestInterceptor { + List suppliers = [] + + AuthRequestInterceptor(JenkinsProperties.JenkinsHost host) { + // Order may be significant here. + if (host.username && host.password) { + suppliers.add(new BasicAuthHeaderSupplier(host.username, host.password)) + } + if (host.jsonPath && host.oauthScopes) { + suppliers.add(new GoogleBearerTokenHeaderSupplier(host.jsonPath, host.oauthScopes)) + } else if (host.token) { + suppliers.add(new BearerTokenHeaderSupplier(token: host.token)) + } + } + + @Override + void intercept(RequestInterceptor.RequestFacade request) { + if (suppliers) { + def values = suppliers.join(", ") + request.addHeader("Authorization", values) + } + } + + static interface AuthorizationHeaderSupplier { + /** + * Returns the value to be added as the value in the "Authorization" HTTP header. + * @return + */ + String toString() + } + + static class BasicAuthHeaderSupplier implements AuthorizationHeaderSupplier { + + private final String username + private final String password + + BasicAuthHeaderSupplier(String username, String password) { + this.username = username + this.password = password + } + + String toString() { + return Credentials.basic(username, password) + } + } + + static class GoogleBearerTokenHeaderSupplier implements AuthorizationHeaderSupplier { + + private GoogleCredentials credentials + + GoogleBearerTokenHeaderSupplier(String jsonPath, List scopes) { + InputStream is = new File(jsonPath).newInputStream() + credentials = GoogleCredentials.fromStream(is).createScoped(scopes) + } + + String toString() { + log.debug("Including Google Bearer token in Authorization header") + credentials.refreshIfExpired() + return credentials.accessToken.tokenValue + } + } + + static class BearerTokenHeaderSupplier implements AuthorizationHeaderSupplier { + private token + + String toString() { + log.debug("Including raw bearer token in Authorization header") + return "Bearer ${token}".toString() + } + } +} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/DefaultJenkinsOkHttpClientProvider.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/DefaultJenkinsOkHttpClientProvider.java similarity index 100% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/DefaultJenkinsOkHttpClientProvider.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/DefaultJenkinsOkHttpClientProvider.java diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/DefaultJenkinsRetrofitRequestInterceptorProvider.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/DefaultJenkinsRetrofitRequestInterceptorProvider.java similarity index 100% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/DefaultJenkinsRetrofitRequestInterceptorProvider.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/DefaultJenkinsRetrofitRequestInterceptorProvider.java diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/JenkinsOkHttpClientProvider.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/JenkinsOkHttpClientProvider.java similarity index 100% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/JenkinsOkHttpClientProvider.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/JenkinsOkHttpClientProvider.java diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/JenkinsRetrofitRequestInterceptorProvider.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/JenkinsRetrofitRequestInterceptorProvider.java similarity index 100% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/config/client/JenkinsRetrofitRequestInterceptorProvider.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/config/client/JenkinsRetrofitRequestInterceptorProvider.java diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/ArtifactNotFoundException.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/ArtifactNotFoundException.java similarity index 90% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/ArtifactNotFoundException.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/ArtifactNotFoundException.java index 62d4687b5..747e4f467 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/ArtifactNotFoundException.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/ArtifactNotFoundException.java @@ -1,7 +1,7 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2019 Google, Inc. * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -16,11 +16,12 @@ package com.netflix.spinnaker.igor.exceptions; +import static org.springframework.http.HttpStatus.NOT_FOUND; + import com.netflix.spinnaker.kork.web.exceptions.NotFoundException; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; -@ResponseStatus(HttpStatus.NOT_FOUND) +@ResponseStatus(NOT_FOUND) public class ArtifactNotFoundException extends NotFoundException { public ArtifactNotFoundException( String master, String job, Integer buildNumber, String fileName) { diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/BuildJobError.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/BuildJobError.java similarity index 89% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/BuildJobError.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/BuildJobError.java index 75c426a8f..8820b01e0 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/BuildJobError.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/BuildJobError.java @@ -1,7 +1,7 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2019 Google, Inc. * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -18,7 +18,6 @@ import com.netflix.spinnaker.kork.web.exceptions.InvalidRequestException; -/** TODO(rz): Sparsely used, but primarily just Jenkins. */ public class BuildJobError extends InvalidRequestException { public BuildJobError(String message) { super(message); diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/QueuedJobDeterminationError.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/QueuedJobDeterminationError.java similarity index 90% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/QueuedJobDeterminationError.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/QueuedJobDeterminationError.java index 23c60244f..377eb507f 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/QueuedJobDeterminationError.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/QueuedJobDeterminationError.java @@ -1,7 +1,7 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2019 Google, Inc. * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -16,7 +16,6 @@ package com.netflix.spinnaker.igor.exceptions; -/** TODO(rz): Document. SpinnakerException. */ public class QueuedJobDeterminationError extends RuntimeException { public QueuedJobDeterminationError(String msg) { super(msg); diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/UnhandledDownstreamServiceErrorException.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/UnhandledDownstreamServiceErrorException.java similarity index 100% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/exceptions/UnhandledDownstreamServiceErrorException.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/exceptions/UnhandledDownstreamServiceErrorException.java diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildContent.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildContent.java deleted file mode 100644 index 582b86260..000000000 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildContent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.gitlabci; - -import com.netflix.spinnaker.igor.build.model.GenericProject; -import com.netflix.spinnaker.igor.history.model.BuildContent; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class GitlabCiBuildContent implements BuildContent { - private static final String TYPE = "gitlab-ci"; - private String master; - private GenericProject project; - - @Override - public String getType() { - return TYPE; - } -} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildEvent.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildEvent.java deleted file mode 100644 index 40ebb436c..000000000 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.gitlabci; - -import com.netflix.spinnaker.igor.history.model.BuildEvent; -import lombok.Data; - -@Data -public class GitlabCiBuildEvent implements BuildEvent { - private GitlabCiBuildContent content; - - public GitlabCiBuildEvent(GitlabCiBuildContent content) { - this.content = content; - } -} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildMonitor.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildMonitor.java index a726c6129..8b6530519 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildMonitor.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/GitlabCiBuildMonitor.java @@ -29,12 +29,14 @@ import com.netflix.spinnaker.igor.gitlabci.service.GitlabCiResultConverter; import com.netflix.spinnaker.igor.gitlabci.service.GitlabCiService; import com.netflix.spinnaker.igor.history.EchoService; +import com.netflix.spinnaker.igor.history.model.GenericBuildContent; +import com.netflix.spinnaker.igor.history.model.GenericBuildEvent; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; import com.netflix.spinnaker.igor.polling.CommonPollingMonitor; import com.netflix.spinnaker.igor.polling.DeltaItem; import com.netflix.spinnaker.igor.polling.LockService; import com.netflix.spinnaker.igor.polling.PollContext; import com.netflix.spinnaker.igor.polling.PollingDelta; -import com.netflix.spinnaker.igor.service.BuildServiceProvider; import com.netflix.spinnaker.igor.service.BuildServices; import com.netflix.spinnaker.security.AuthenticatedRequest; import java.util.ArrayList; @@ -225,10 +227,14 @@ private void sendEvent( slug, GitlabCiPipelineUtis.genericBuild(pipeline, project.getPathWithNamespace(), address)); - GitlabCiBuildContent content = new GitlabCiBuildContent(master, genericProject); + GenericBuildContent content = new GenericBuildContent(); + content.setMaster(master); + content.setType("gitlab-ci"); + content.setProject(genericProject); - AuthenticatedRequest.allowAnonymous( - () -> echoService.get().postEvent(new GitlabCiBuildEvent(content))); + GenericBuildEvent event = new GenericBuildEvent(); + event.setContent(content); + AuthenticatedRequest.allowAnonymous(() -> echoService.get().postEvent(event)); } @Override diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiPipelineUtis.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiPipelineUtis.java index b17a05655..634654250 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiPipelineUtis.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiPipelineUtis.java @@ -27,23 +27,23 @@ public static String getBranchedPipelineSlug(final Project project, final Pipeli } public static GenericBuild genericBuild(Pipeline pipeline, String repoSlug, String baseUrl) { - GenericBuild.GenericBuildBuilder builder = - GenericBuild.builder() - .building(GitlabCiResultConverter.running(pipeline.getStatus())) - .number(pipeline.getId()) - .duration(pipeline.getDuration()) - .result(GitlabCiResultConverter.getResultFromGitlabCiState(pipeline.getStatus())) - .name(repoSlug) - .url(url(repoSlug, baseUrl, pipeline.getId())); + GenericBuild genericBuild = new GenericBuild(); + genericBuild.setBuilding(GitlabCiResultConverter.running(pipeline.getStatus())); + genericBuild.setNumber(pipeline.getId()); + genericBuild.setDuration(pipeline.getDuration()); + genericBuild.setResult( + GitlabCiResultConverter.getResultFromGitlabCiState(pipeline.getStatus())); + genericBuild.setName(repoSlug); + genericBuild.setUrl(url(repoSlug, baseUrl, pipeline.getId())); if (pipeline.getFinishedAt() != null) { - builder.timestamp(Long.toString(pipeline.getFinishedAt().getTime())); + genericBuild.setTimestamp(Long.toString(pipeline.getFinishedAt().getTime())); } - return builder.build(); + return genericBuild; } private static String url(final String repoSlug, final String baseUrl, final int id) { - return baseUrl + "/" + repoSlug + "/pipelines/" + id; + return baseUrl + "/" + repoSlug + "/pipelines/" + String.valueOf(id); } } diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiService.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiService.java index a9bf2338a..e2c1a32c3 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiService.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/gitlabci/service/GitlabCiService.java @@ -23,8 +23,8 @@ import com.netflix.spinnaker.igor.gitlabci.client.model.Pipeline; import com.netflix.spinnaker.igor.gitlabci.client.model.PipelineSummary; import com.netflix.spinnaker.igor.gitlabci.client.model.Project; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; import com.netflix.spinnaker.igor.service.BuildOperations; -import com.netflix.spinnaker.igor.service.BuildServiceProvider; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/BuildContent.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/BuildContent.groovy new file mode 100644 index 000000000..ebcf22096 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/BuildContent.groovy @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.history.model + +import com.netflix.spinnaker.igor.jenkins.client.model.Project + +/** + * Encapsulates a build content block + * + * TODO(rz): Cannot move to kork-core due to Jenkins dependency + */ +class BuildContent { + Project project + String master +} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/QueuedExecutable.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/BuildEvent.groovy similarity index 62% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/QueuedExecutable.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/BuildEvent.groovy index 6ffd716de..b612497f7 100644 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/QueuedExecutable.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/BuildEvent.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.jenkins.client.model; -import javax.xml.bind.annotation.XmlElement; +package com.netflix.spinnaker.igor.history.model -public class QueuedExecutable { - public Integer getNumber() { - return number; - } +/** + * A history entry that contains a build detail + * + * TODO(rz): Cannot move to kork-core due to Jenkins dependency + */ +class BuildEvent extends Event { - public void setNumber(Integer number) { - this.number = number; - } + BuildContent content + Map details = [ + type : 'build', + source: 'igor' + ] - @XmlElement private Integer number; } diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/BuildContent.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/GenericBuildContent.groovy similarity index 65% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/BuildContent.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/GenericBuildContent.groovy index 2899df70c..7fa34b95c 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/BuildContent.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/GenericBuildContent.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2016 Schibsted ASA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.history.model; -/** TODO(rz): Documnt. */ -public interface BuildContent { +package com.netflix.spinnaker.igor.history.model - String UNDEFINED_TYPE = "undefined"; +import com.netflix.spinnaker.igor.build.model.GenericProject - default String getType() { - return UNDEFINED_TYPE; - } +/** + * TODO(rz): Cannot move to kork-core due to Jenkins dependency + */ +class GenericBuildContent { + GenericProject project + String master + String type } diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/history/model/JenkinsBuildEvent.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/GenericBuildEvent.groovy similarity index 65% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/history/model/JenkinsBuildEvent.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/GenericBuildEvent.groovy index c7e644d92..9f3e0a0f4 100644 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/history/model/JenkinsBuildEvent.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/history/model/GenericBuildEvent.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2016 Schibsted ASA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.history.model; -import lombok.AllArgsConstructor; -import lombok.Data; +package com.netflix.spinnaker.igor.history.model -@Data -@AllArgsConstructor -public class JenkinsBuildEvent implements BuildEvent { - private JenkinsBuildContent content; +/** + * TODO(rz): Cannot move to kork-core due to Jenkins dependency + */ +class GenericBuildEvent extends Event{ + GenericBuildContent content + Map details = [ + type : 'build', + source: 'igor' + ] } diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitor.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitor.groovy new file mode 100644 index 000000000..efcf5f1d0 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitor.groovy @@ -0,0 +1,246 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins + +import com.netflix.discovery.DiscoveryClient +import com.netflix.spectator.api.BasicTag +import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.igor.IgorConfigurationProperties +import com.netflix.spinnaker.igor.config.JenkinsProperties +import com.netflix.spinnaker.igor.history.EchoService +import com.netflix.spinnaker.igor.history.model.BuildContent +import com.netflix.spinnaker.igor.history.model.BuildEvent +import com.netflix.spinnaker.igor.jenkins.client.model.Build +import com.netflix.spinnaker.igor.jenkins.client.model.Project +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService +import com.netflix.spinnaker.igor.model.BuildServiceProvider +import com.netflix.spinnaker.igor.polling.CommonPollingMonitor +import com.netflix.spinnaker.igor.polling.DeltaItem +import com.netflix.spinnaker.igor.polling.LockService +import com.netflix.spinnaker.igor.polling.PollContext +import com.netflix.spinnaker.igor.polling.PollingDelta +import com.netflix.spinnaker.igor.service.BuildServices +import com.netflix.spinnaker.security.AuthenticatedRequest +import groovy.time.TimeCategory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Service +import retrofit.RetrofitError +import java.util.stream.Collectors + +import static net.logstash.logback.argument.StructuredArguments.kv +/** + * Monitors new jenkins builds + */ +@Service +@SuppressWarnings('CatchException') +@ConditionalOnProperty('jenkins.enabled') +class JenkinsBuildMonitor extends CommonPollingMonitor { + + private final JenkinsCache cache + private final BuildServices buildServices + private final boolean pollingEnabled + private final Optional echoService + private final JenkinsProperties jenkinsProperties + + @Autowired + JenkinsBuildMonitor(IgorConfigurationProperties properties, + Registry registry, + Optional discoveryClient, + Optional lockService, + JenkinsCache cache, + BuildServices buildServices, + @Value('${jenkins.polling.enabled:true}') boolean pollingEnabled, + Optional echoService, + JenkinsProperties jenkinsProperties) { + super(properties, registry, discoveryClient, lockService) + this.cache = cache + this.buildServices = buildServices + this.pollingEnabled = pollingEnabled + this.echoService = echoService + this.jenkinsProperties = jenkinsProperties + } + + @Override + String getName() { + "jenkinsBuildMonitor" + } + + @Override + boolean isInService() { + pollingEnabled && super.isInService() + } + + @Override + void poll(boolean sendEvents) { + buildServices.getServiceNames(BuildServiceProvider.JENKINS).stream().forEach( + { master -> pollSingle(new PollContext(master, !sendEvents)) } + ) + } + + /** + * Gets a list of jobs for this master & processes builds between last poll stamp and a sliding upper bound stamp, + * the cursor will be used to advanced to the upper bound when all builds are completed in the commit phase. + */ + @Override + protected JobPollingDelta generateDelta(PollContext ctx) { + String master = ctx.partitionName + log.trace("Checking for new builds for $master") + + final List delta = [] + registry.timer("pollingMonitor.jenkins.retrieveProjects", [new BasicTag("partition", master)]).record { + JenkinsService jenkinsService = buildServices.getService(master) as JenkinsService + List jobs = jenkinsService.getProjects()?.getList() ?:[] + jobs.forEach( { job -> processBuildsOfProject(jenkinsService, master, job, delta)}) + } + return new JobPollingDelta(master: master, items: delta) + } + + private void processBuildsOfProject(JenkinsService jenkinsService, String master, Project job, List delta) { + if (!job.lastBuild) { + log.trace("[{}:{}] has no builds skipping...", kv("master", master), kv("job", job.name)) + return + } + + try { + Long cursor = cache.getLastPollCycleTimestamp(master, job.name) + Long lastBuildStamp = job.lastBuild.timestamp as Long + Date upperBound = new Date(lastBuildStamp) + if (cursor == lastBuildStamp) { + log.trace("[${master}:${job.name}] is up to date. skipping") + return + } + + if (!cursor && !igorProperties.spinnaker.build.handleFirstBuilds) { + cache.setLastPollCycleTimestamp(master, job.name, lastBuildStamp) + return + } + + List allBuilds = getBuilds(jenkinsService, master, job, cursor, lastBuildStamp) + List currentlyBuilding = allBuilds.findAll { it.building } + List completedBuilds = allBuilds.findAll { !it.building } + cursor = !cursor ? lastBuildStamp : cursor + Date lowerBound = new Date(cursor) + + if (!igorProperties.spinnaker.build.processBuildsOlderThanLookBackWindow) { + completedBuilds = onlyInLookBackWindow(completedBuilds) + } + + delta.add(new JobDelta( + cursor: cursor, + name: job.name, + lastBuildStamp: lastBuildStamp, + upperBound: upperBound, + lowerBound: lowerBound, + completedBuilds: completedBuilds, + runningBuilds: currentlyBuilding + )) + + } catch (e) { + log.error("Error processing builds for [{}:{}]", kv("master", master), kv("job", job.name), e) + if (e.cause instanceof RetrofitError) { + def re = (RetrofitError) e.cause + log.error("Error communicating with jenkins for [{}:{}]: {}", kv("master", master), kv("job", job.name), kv("url", re.url), re) + } + } + } + + private List getBuilds(JenkinsService jenkinsService, String master, Project job, Long cursor, Long lastBuildStamp) { + if (!cursor) { + log.debug("[${master}:${job.name}] setting new cursor to ${lastBuildStamp}") + return jenkinsService.getBuilds(job.name) ?: [] + } + + // filter between last poll and jenkins last build included + return (jenkinsService.getBuilds(job.name) ?: []).findAll { build -> + Long buildStamp = build.timestamp as Long + return buildStamp <= lastBuildStamp && buildStamp > cursor + } + } + + private List onlyInLookBackWindow(List builds) { + use(TimeCategory) { + def offsetSeconds = pollInterval.seconds + def lookBackWindowMins = igorProperties.spinnaker.build.lookBackWindowMins.minutes + Date lookBackDate = (offsetSeconds + lookBackWindowMins).ago + + return builds.stream().filter({ + Date buildEndDate = new Date((it.timestamp as Long) + it.duration) + return buildEndDate.after(lookBackDate) + }).collect(Collectors.toList()) + } + } + + @Override + protected void commitDelta(JobPollingDelta delta, boolean sendEvents) { + String master = delta.master + + delta.items.stream().forEach { job -> + // post events for finished builds + job.completedBuilds.forEach { build -> + Boolean eventPosted = cache.getEventPosted(master, job.name, job.cursor, build.number) + if (!eventPosted) { + if (sendEvents) { + postEvent(new Project(name: job.name, lastBuild: build), master) + log.debug("[${master}:${job.name}]:${build.number} event posted") + cache.setEventPosted(master, job.name, job.cursor, build.number) + } + } + } + + // advance cursor when all builds have completed in the interval + if (job.runningBuilds.isEmpty()) { + log.info("[{}:{}] has no other builds between [${job.lowerBound} - ${job.upperBound}], " + + "advancing cursor to ${job.lastBuildStamp}", kv("master", master), kv("job", job.name)) + cache.pruneOldMarkers(master, job.name, job.cursor) + cache.setLastPollCycleTimestamp(master, job.name, job.lastBuildStamp) + } + } + } + + @Override + protected Integer getPartitionUpperThreshold(String partition) { + return jenkinsProperties.masters.find { partition == it.name }?.itemUpperThreshold + } + + private void postEvent(Project project, String master) { + if (!echoService.isPresent()) { + log.warn("Cannot send build notification: Echo is not configured") + registry.counter(missedNotificationId.withTag("monitor", getClass().simpleName)).increment() + return + } + AuthenticatedRequest.allowAnonymous { + echoService.get().postEvent(new BuildEvent(content: new BuildContent(project: project, master: master))) + } + } + + private static class JobPollingDelta implements PollingDelta { + String master + List items + } + + private static class JobDelta implements DeltaItem { + Long cursor + String name + Long lastBuildStamp + Date lowerBound + Date upperBound + List completedBuilds + List runningBuilds + } +} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsCache.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsCache.java similarity index 96% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsCache.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsCache.java index 1510ab6c6..f323b0510 100644 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsCache.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsCache.java @@ -1,7 +1,7 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2017 Netflix, Inc. * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -106,7 +106,6 @@ public void setLastPollCycleTimestamp(String master, String job, Long timestamp) }); } - /** TODO(rz): Use primitive type. Return -1 if there's no prior timestamp. */ public Long getLastPollCycleTimestamp(String master, String job) { return redisClientDelegate.withCommandsClient( c -> { @@ -115,7 +114,7 @@ public Long getLastPollCycleTimestamp(String master, String job) { }); } - public boolean getEventPosted(String master, String job, Long cursor, Integer buildNumber) { + public Boolean getEventPosted(String master, String job, Long cursor, Integer buildNumber) { String key = makeKey(master, job) + ":" + POLL_STAMP + ":" + cursor; return redisClientDelegate.withCommandsClient( c -> c.hget(key, Integer.toString(buildNumber)) != null); diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClient.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClient.groovy new file mode 100644 index 000000000..a474f7f02 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClient.groovy @@ -0,0 +1,89 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client + +import com.netflix.spinnaker.igor.jenkins.client.model.* +import com.netflix.spinnaker.igor.jenkins.client.model.BuildsList +import com.netflix.spinnaker.igor.jenkins.client.model.JobConfig +import com.netflix.spinnaker.igor.jenkins.client.model.JobList +import com.netflix.spinnaker.igor.jenkins.client.model.ScmDetails +import com.netflix.spinnaker.igor.model.Crumb +import retrofit.client.Response +import retrofit.http.* + +/** + * Interface for interacting with a Jenkins Service via Xml + */ +@SuppressWarnings('LineLength') +interface JenkinsClient { + /* + * Jobs created with the Jenkins Folders plugin are nested. + * Some queries look for jobs within folders with a depth of 10. + */ + + @GET('/api/xml?tree=jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url],jobs[name,lastBuild[actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url]]]]]]]]]]]&exclude=/*/*/*/action[not(totalCount)]') + ProjectsList getProjects() + + @GET('/api/xml?tree=jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name,jobs[name]]]]]]]]]]') + JobList getJobs() + + @GET('/job/{jobName}/api/xml?exclude=/*/build/action[not(totalCount)]&tree=builds[number,url,duration,timestamp,result,building,url,fullDisplayName,actions[failCount,skipCount,totalCount]]') + BuildsList getBuilds(@EncodedPath('jobName') String jobName) + + @GET('/job/{jobName}/api/xml?tree=name,url,actions[processes[name]],downstreamProjects[name,url],upstreamProjects[name,url]') + BuildDependencies getDependencies(@EncodedPath('jobName') String jobName) + + @GET('/job/{jobName}/{buildNumber}/api/xml?exclude=/*/action[not(totalCount)]&tree=actions[failCount,skipCount,totalCount,urlName],duration,number,timestamp,result,building,url,fullDisplayName,artifacts[displayPath,fileName,relativePath]') + Build getBuild(@EncodedPath('jobName') String jobName, @Path('buildNumber') Integer buildNumber) + + // The location of the SCM details in the build xml changed in version 4.0.0 of the jenkins-git plugin; see the + // header comment in com.netflix.spinnaker.igor.jenkins.client.model.ScmDetails for more information. + // The exclude and tree parameters to this call must continue to support both formats to remain compatible with + // all versions of the plugin. + @GET('/job/{jobName}/{buildNumber}/api/xml?exclude=/*/action[not(build|lastBuiltRevision)]&tree=actions[remoteUrls,lastBuiltRevision[branch[name,SHA1]],build[revision[branch[name,SHA1]]]]') + ScmDetails getGitDetails(@EncodedPath('jobName') String jobName, @Path('buildNumber') Integer buildNumber) + + @GET('/job/{jobName}/lastCompletedBuild/api/xml') + Build getLatestBuild(@EncodedPath('jobName') String jobName) + + @GET('/queue/item/{itemNumber}/api/xml') + QueuedJob getQueuedItem(@Path('itemNumber') Integer item) + + @POST('/job/{jobName}/build') + Response build(@EncodedPath('jobName') String jobName, @Body String emptyRequest, @Header("Jenkins-Crumb") String crumb) + + @POST('/job/{jobName}/buildWithParameters') + Response buildWithParameters(@EncodedPath('jobName') String jobName, @QueryMap Map queryParams, @Body String EmptyRequest, @Header("Jenkins-Crumb") String crumb) + + @POST('/job/{jobName}/{buildNumber}/stop') + Response stopRunningBuild(@EncodedPath('jobName') String jobName, @Path('buildNumber') Integer buildNumber, @Body String EmptyRequest, @Header("Jenkins-Crumb") String crumb) + + @POST('/queue/cancelItem') + Response stopQueuedBuild(@Query('id') String queuedBuild, @Body String emptyRequest, @Header("Jenkins-Crumb") String crumb) + + @GET('/job/{jobName}/api/xml?exclude=/*/action&exclude=/*/build&exclude=/*/property[not(parameterDefinition)]') + JobConfig getJobConfig(@EncodedPath('jobName') String jobName) + + @Streaming + @GET('/job/{jobName}/{buildNumber}/artifact/{fileName}') + Response getPropertyFile( + @EncodedPath('jobName') String jobName, + @Path('buildNumber') Integer buildNumber, @Path(value = 'fileName', encode = false) String fileName) + + @GET('/crumbIssuer/api/xml') + Crumb getCrumb() +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsMasters.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsMasters.groovy new file mode 100644 index 000000000..e2056bce9 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsMasters.groovy @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client + +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService + +/** + * Wrapper class for a collection of jenkins clients + */ +class JenkinsMasters { + + Map map + +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/Build.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/Build.groovy new file mode 100644 index 000000000..d1f25385a --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/Build.groovy @@ -0,0 +1,80 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import com.netflix.spinnaker.igor.build.model.GenericArtifact +import com.netflix.spinnaker.igor.build.model.GenericBuild +import com.netflix.spinnaker.igor.build.model.Result +import groovy.transform.CompileStatic + +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement + +/** + * Represents a build in Jenkins + */ +@CompileStatic +@JsonInclude(JsonInclude.Include.NON_NULL) +@XmlRootElement +class Build { + boolean building + Integer number + @XmlElement(required = false) + String result + String timestamp + @XmlElement(required = false) + Long duration + @XmlElement(required = false) + Integer estimatedDuration + @XmlElement(required = false) + String id + String url + @XmlElement(required = false) + String builtOn + @XmlElement(required = false) + String fullDisplayName + + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "artifact", required = false) + List artifacts + + /* + We need to dump this into a list first since the Jenkins query returns + multiple action elements, with all but the test run one empty. We then filter it into a testResults var + */ + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "action", required = false) + List testResults + + GenericBuild genericBuild(String jobName) { + GenericBuild genericBuild = new GenericBuild(building: building, number: number.intValue(), duration: duration.intValue(), result: result as Result, name: jobName, url: url, timestamp: timestamp, fullDisplayName: fullDisplayName) + if (artifacts) { + genericBuild.artifacts = artifacts.collect { buildArtifact -> + GenericArtifact artifact = buildArtifact.getGenericArtifact() + artifact.name = jobName + artifact.version = number + artifact + } + } + if (testResults) { + genericBuild.testResults = testResults + } + return genericBuild + } +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildArtifact.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildArtifact.groovy new file mode 100644 index 000000000..7f1334f58 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildArtifact.groovy @@ -0,0 +1,49 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import com.netflix.spinnaker.igor.build.model.GenericArtifact +import groovy.transform.CompileStatic +import org.simpleframework.xml.Default +import org.simpleframework.xml.Element +import org.simpleframework.xml.Root + +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement + +/** + * Represents a build artifact + */ +@CompileStatic +@XmlRootElement(name = 'artifact') +class BuildArtifact { + @XmlElement(required = false) + String fileName + + @XmlElement(required = false) + String displayPath + + @XmlElement(required = false) + String relativePath + + GenericArtifact getGenericArtifact() { + GenericArtifact artifact = new GenericArtifact(fileName, displayPath, relativePath) + artifact.type = 'jenkins/file' + artifact.reference = relativePath + return artifact + } +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependencies.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependencies.groovy new file mode 100644 index 000000000..ce6ff0701 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependencies.groovy @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import groovy.transform.CompileStatic + +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement + +/** + * Captures build dependencies for a jenkins job + */ +@XmlRootElement +@CompileStatic +class BuildDependencies { + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "downstreamProject", required = false) + List downstreamProjects + + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "upstreamProject", required = false) + List upstreamProjects +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependency.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependency.groovy new file mode 100644 index 000000000..181267181 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildDependency.groovy @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import groovy.transform.CompileStatic +import org.simpleframework.xml.Default + +/** + * Represents either an upstream or downstream dependency in Jenkins + */ +@Default +@CompileStatic +class BuildDependency { + String name + String url +} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Revision.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildsList.groovy similarity index 54% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Revision.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildsList.groovy index 95480661c..5671fa504 100644 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/Revision.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/BuildsList.groovy @@ -1,11 +1,11 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,22 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.jenkins.client.model; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import java.util.List; -import javax.xml.bind.annotation.XmlElement; +package com.netflix.spinnaker.igor.jenkins.client.model -public class Revision { - public List getBranch() { - return branch; - } +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import groovy.transform.CompileStatic - public void setBranch(List branch) { - this.branch = branch; - } +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement - @JacksonXmlElementWrapper(useWrapping = false) - @XmlElement(name = "branch") - private List branch; +/** + * Represents a list of builds + */ +@XmlRootElement +@CompileStatic +class BuildsList { + @JacksonXmlElementWrapper(useWrapping=false) + @XmlElement(name = 'build') + List list } diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/DefaultParameterValue.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/DefaultParameterValue.java similarity index 100% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/DefaultParameterValue.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/DefaultParameterValue.java diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/DownstreamProject.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/DownstreamProject.groovy new file mode 100644 index 000000000..3591288cd --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/DownstreamProject.groovy @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import org.simpleframework.xml.Root + +/** + * Represents a Jenkins job downstream project + */ +@Root(name = 'downstreamProject', strict=false) +class DownstreamProject extends RelatedProject { +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/JobConfig.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/JobConfig.groovy new file mode 100644 index 000000000..b9dc18041 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/JobConfig.groovy @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import com.netflix.spinnaker.igor.build.model.JobConfiguration + +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlElementWrapper +import javax.xml.bind.annotation.XmlRootElement + +/** + * Represents the basic Jenkins job configuration information + */ +@XmlRootElement +class JobConfig implements JobConfiguration { + @XmlElement(required = false) + String description + + @XmlElement + String displayName + + @XmlElement + String name + + @XmlElement + boolean buildable + + @XmlElement + String color + + @XmlElement + String url + + @XmlElementWrapper(name = "property") + @XmlElement(name = "parameterDefinition", required = false) + List parameterDefinitionList + + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "upstreamProject", required = false) + List upstreamProjectList + + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "downstreamProject", required = false) + List downstreamProjectList + + @XmlElement + boolean concurrentBuild +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/JobList.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/JobList.groovy new file mode 100644 index 000000000..496522d43 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/JobList.groovy @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import groovy.transform.CompileStatic + +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement + +/** + * Represents a list of projects + */ +@XmlRootElement(name = 'hudson') +@CompileStatic +class JobList { + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "job") + List list +} + +class Job { + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "job") + List list + + @XmlElement(required = false) + String name +} diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ParameterDefinition.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ParameterDefinition.java similarity index 100% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ParameterDefinition.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ParameterDefinition.java diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/Project.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/Project.groovy new file mode 100644 index 000000000..a94bb927a --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/Project.groovy @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import groovy.transform.CompileStatic + +import javax.xml.bind.annotation.XmlElement + +/** + * Represents a Project returned by the Jenkins service in the project list + */ +@CompileStatic +class Project { + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "job", required = false) + List list + + @XmlElement + String name + + @XmlElement + Build lastBuild +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ProjectsList.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ProjectsList.groovy new file mode 100644 index 000000000..298863857 --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ProjectsList.groovy @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import groovy.transform.CompileStatic + +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement + +/** + * Represents a list of projects + */ +@XmlRootElement(name = 'hudson') +@CompileStatic +class ProjectsList { + + // not sure why we need this, it seems that there shouldn't be a element wrapping the list + // since we don't have the JAXB @XmlElementWrapper annotation, but for some reason Jackson expects one + @JacksonXmlElementWrapper(useWrapping=false) + @XmlElement(name = 'job') + List list +} diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/BuildEvent.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/QueuedJob.groovy similarity index 52% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/BuildEvent.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/QueuedJob.groovy index d55e1b20d..1cce114b2 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/history/model/BuildEvent.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/QueuedJob.groovy @@ -1,11 +1,11 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,20 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.history.model; -import java.util.HashMap; -import java.util.Map; +package com.netflix.spinnaker.igor.jenkins.client.model -/** TODO(rz): Documnt. */ -public interface BuildEvent extends Event { - T getContent(); +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement - default Map getDetails() { - Map d = new HashMap<>(); - d.put("type", "build"); - d.put("source", "igor"); - return d; - } +@XmlRootElement +class QueuedJob { + @XmlElement + QueuedExecutable executable + + @XmlElement(name = 'number') + Integer getNumber() { + return executable?.number + } +} + + +class QueuedExecutable { + @XmlElement + Integer number } diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ScmBuild.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/RelatedProject.groovy similarity index 55% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ScmBuild.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/RelatedProject.groovy index 89bb1abf6..74cb8d6d2 100644 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/client/model/ScmBuild.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/RelatedProject.groovy @@ -1,11 +1,11 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.jenkins.client.model; -import javax.xml.bind.annotation.XmlElement; +package com.netflix.spinnaker.igor.jenkins.client.model -public class ScmBuild { - public Revision getRevision() { - return revision; - } +import org.simpleframework.xml.Element +import org.simpleframework.xml.Root - public void setRevision(Revision revision) { - this.revision = revision; - } +/** + * Represents a upstream/downstream project for a Jenkins job + */ +@Root(strict=false) +class RelatedProject { + @Element + String name + + @Element + String url - @XmlElement(required = false) - private Revision revision; + @Element + String color } diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ScmDetails.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ScmDetails.groovy new file mode 100644 index 000000000..7d6b5116d --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/ScmDetails.groovy @@ -0,0 +1,118 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper +import com.netflix.spinnaker.igor.build.model.GenericGitRevision +import groovy.transform.CompileStatic + +import javax.xml.bind.annotation.XmlElement +import java.util.stream.Collectors + +/** + * Represents git details + * + * The serialization of these details in the Jenkins build XML changed in version 4.0.0 of the jenkins-git plugin. + * + * Prior to 4.0.0, the format was: + * + * + * + * 943a702d06f34599aee1f8da8ef9f7296031d699 + * refs/remotes/origin/master + * + * + * some-url + * + * + * As of version 4.0.0, the format is: + * + * + * + * + * 943a702d06f34599aee1f8da8ef9f7296031d699 + * refs/remotes/origin/master + * + * + * some-url + * + * + * + * The code in this module should remain compatible with both formats to ensure that SCM info is populated in Spinnaker + * regardless of which version of the jenkins-git plugin is being used. + */ +@CompileStatic +class ScmDetails { + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "action") + ArrayList actions + + List genericGitRevisions() { + List genericGitRevisions = new ArrayList() + + if (actions == null) { + return null + } + + for (Action action : actions) { + Revision revision = action?.lastBuiltRevision ?: action?.build?.revision + if (revision?.branch?.name) { + genericGitRevisions.addAll(revision.branch.collect() { Branch branch -> + GenericGitRevision.builder() + .name(branch.getName()) + .branch(branch.name.split('/').last()) + .sha1(branch.sha1) + .remoteUrl(action.remoteUrl) + .build() + }) + } + } + + // If the same revision appears in both the old and the new location in the XML, we only want to return it once + return genericGitRevisions.stream().distinct().collect(Collectors.toList()) + } +} + +class Action { + @XmlElement(required = false) + Revision lastBuiltRevision + + @XmlElement(required = false) + ScmBuild build + + @XmlElement(required = false) + String remoteUrl +} + +class ScmBuild { + @XmlElement(required = false) + Revision revision +} + +class Revision { + @JacksonXmlElementWrapper(useWrapping = false) + @XmlElement(name = "branch") + List branch +} + +class Branch { + @XmlElement(required = false) + String name + + @XmlElement(required = false, name = "SHA1") + String sha1 +} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/TestResults.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/TestResults.groovy new file mode 100644 index 000000000..65146f1fe --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/TestResults.groovy @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.jenkins.client.model + +import groovy.transform.CompileStatic +import org.simpleframework.xml.Element +import org.simpleframework.xml.Root + +/** + * Represents a build artifact + */ +@CompileStatic +@Root(name = 'action', strict = false) +class TestResults { + @Element(required = false) + int failCount + @Element(required = false) + int skipCount + @Element(required = false) + int totalCount + @Element(required = false) + String urlName +} diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/JobNamesProvider.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/UpstreamProject.groovy similarity index 68% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/service/JobNamesProvider.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/UpstreamProject.groovy index 4f188ca36..92fa63722 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/JobNamesProvider.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/client/model/UpstreamProject.groovy @@ -1,11 +1,11 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.spinnaker.igor.service; -import java.util.List; +package com.netflix.spinnaker.igor.jenkins.client.model -public interface JobNamesProvider { - List getJobNames(); +/** + * Represents a Jenkins job upstream project + */ +class UpstreamProject extends RelatedProject { } diff --git a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsService.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/service/JenkinsService.java similarity index 60% rename from igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsService.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/service/JenkinsService.java index 6f37536df..d8f6a6e64 100644 --- a/igor-monitor-jenkins/src/main/java/com/netflix/spinnaker/igor/jenkins/JenkinsService.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/jenkins/service/JenkinsService.java @@ -1,11 +1,11 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,17 +14,13 @@ * limitations under the License. */ -package com.netflix.spinnaker.igor.jenkins; +package com.netflix.spinnaker.igor.jenkins.service; -import static java.lang.String.format; import static net.logstash.logback.argument.StructuredArguments.kv; import static org.springframework.http.HttpStatus.NOT_FOUND; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; import com.netflix.spinnaker.fiat.model.resources.Permissions; import com.netflix.spinnaker.hystrix.SimpleJava8HystrixCommand; import com.netflix.spinnaker.igor.build.model.GenericBuild; @@ -33,18 +29,30 @@ import com.netflix.spinnaker.igor.exceptions.BuildJobError; import com.netflix.spinnaker.igor.exceptions.QueuedJobDeterminationError; import com.netflix.spinnaker.igor.jenkins.client.JenkinsClient; -import com.netflix.spinnaker.igor.jenkins.client.model.*; -import com.netflix.spinnaker.igor.jenkins.exceptions.InvalidJobParameterException; -import com.netflix.spinnaker.igor.service.*; +import com.netflix.spinnaker.igor.jenkins.client.model.Build; +import com.netflix.spinnaker.igor.jenkins.client.model.BuildArtifact; +import com.netflix.spinnaker.igor.jenkins.client.model.BuildDependencies; +import com.netflix.spinnaker.igor.jenkins.client.model.JobConfig; +import com.netflix.spinnaker.igor.jenkins.client.model.JobList; +import com.netflix.spinnaker.igor.jenkins.client.model.Project; +import com.netflix.spinnaker.igor.jenkins.client.model.ProjectsList; +import com.netflix.spinnaker.igor.jenkins.client.model.QueuedJob; +import com.netflix.spinnaker.igor.jenkins.client.model.ScmDetails; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; +import com.netflix.spinnaker.igor.model.Crumb; +import com.netflix.spinnaker.igor.service.BuildOperations; +import com.netflix.spinnaker.igor.service.BuildProperties; import com.netflix.spinnaker.kork.core.RetrySupport; import com.netflix.spinnaker.kork.exceptions.SpinnakerException; import com.netflix.spinnaker.kork.web.exceptions.NotFoundException; import java.io.InputStream; import java.time.Duration; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.web.util.UriUtils; @@ -55,11 +63,7 @@ import retrofit.client.Response; @Slf4j -public class JenkinsService - implements BuildProperties, BuildQueueOperations, JobNamesProvider { - - private static final Splitter QUEUED_BUILD_SPLITTER = Splitter.on("/"); - +public class JenkinsService implements BuildOperations, BuildProperties { private final ObjectMapper objectMapper = new ObjectMapper(); private final String groupKey; private final String serviceName; @@ -117,35 +121,7 @@ private Stream recursiveGetProjects(Project project, String prefix) { return project.getList().stream().flatMap(p -> recursiveGetProjects(p, projectName + "/job/")); } - @Override - public List getJobNames() { - return recursiveJobNames(getJobs().getList(), null); - } - - private static List recursiveJobNames(List jobs, String prefix) { - List jobNames = new ArrayList<>(); - - final String pre = (Strings.isNullOrEmpty(prefix)) ? null : prefix + "/job/"; - jobs.forEach( - job -> { - String qualifiedName; - if (pre == null) { - qualifiedName = job.getName(); - } else { - qualifiedName = pre + job.getName(); - } - - if (job.getList() == null || job.getList().isEmpty()) { - jobNames.add(qualifiedName); - } else { - jobNames.addAll(recursiveJobNames(job.getList(), qualifiedName)); - } - }); - - return jobNames; - } - - private JobList getJobs() { + public JobList getJobs() { return new SimpleJava8HystrixCommand<>( groupKey, buildCommandKey("getJobs"), jenkinsClient::getJobs) .execute(); @@ -192,88 +168,25 @@ public GenericBuild getGenericBuild(String jobName, int buildNumber) { @Override public int triggerBuildWithParameters(String job, Map queryParameters) { - JobConfig jobConfig = getJobConfig(job); - if (!jobConfig.isBuildable()) { - throw new BuildJobError(format("Job '%s' is not buildable. It may be disabled.", job)); - } - - if (jobConfig.getParameterDefinitionList() != null - && !jobConfig.getParameterDefinitionList().isEmpty()) { - validateJobParameters(jobConfig, queryParameters); - } - - Response response; - if (!queryParameters.isEmpty() - && jobConfig.getParameterDefinitionList() != null - && !jobConfig.getParameterDefinitionList().isEmpty()) { - response = buildWithParameters(job, queryParameters); - } else if (queryParameters.isEmpty() - && jobConfig.getParameterDefinitionList() != null - && !jobConfig.getParameterDefinitionList().isEmpty()) { - // account for when you just want to fire a job with the default parameter values by adding a - // dummy param - response = buildWithParameters(job, Collections.singletonMap("startedBy", "igor")); - } else if (queryParameters.isEmpty() - && (jobConfig.getParameterDefinitionList() == null - || jobConfig.getParameterDefinitionList().isEmpty())) { - response = build(job); - } else { - // Jenkins will reject the build, so don't even try - // we should throw a BuildJobError, but I get a bytecode error : java.lang.VerifyError: Bad - // method call from inside of a branch - throw new RuntimeException( - format("job : %s, passing params to a job which doesn't need them", job)); - } - - return getBuildNumberFromResponse(job, response); - } - - static int getBuildNumberFromResponse(String job, Response response) { + Response response = buildWithParameters(job, queryParameters); if (response.getStatus() != 201) { - // TODO(rz): How to determine master? - throw new BuildJobError( - format("Received a non-201 status when submitting job '%s' to master 'unknown'", job)); + throw new BuildJobError("Received a non-201 status when submitting job '" + job + "'"); } log.info("Submitted build job '{}'", kv("job", job)); - Header locationHeader = + String queuedLocation = response.getHeaders().stream() - .filter(it -> it.getName().toLowerCase().equals("location")) + .filter(h -> h.getName() != null) + .filter(h -> h.getName().toLowerCase().equals("location")) + .map(Header::getValue) .findFirst() .orElseThrow( () -> new QueuedJobDeterminationError( - format("Could not find Location header for job '%s'", job))); - String queuedLocation = locationHeader.getValue(); + "Could not find Location header for job '" + job + "'")); - String[] parts = - StreamSupport.stream(QUEUED_BUILD_SPLITTER.split(queuedLocation).spliterator(), false) - .toArray(String[]::new); - return Integer.parseInt(parts[parts.length - 1]); - } - - static void validateJobParameters(JobConfig jobConfig, Map requestParams) { - if (jobConfig.getParameterDefinitionList() == null) { - return; - } - - jobConfig - .getParameterDefinitionList() - .forEach( - (parameterDefinition) -> { - String matchingParam = requestParams.get(parameterDefinition.getName()); - if (matchingParam != null - && "ChoiceParameterDefinition".equals(parameterDefinition.type) - && parameterDefinition.choices != null - && !parameterDefinition.choices.contains(matchingParam)) { - throw new InvalidJobParameterException( - format( - "'%s' is not a valid choice for '%s'. Valid choices are: %s", - matchingParam, - parameterDefinition.name, - Joiner.on(", ").join(parameterDefinition.choices))); - } - }); + int lastSlash = queuedLocation.lastIndexOf('/'); + return Integer.parseInt(queuedLocation.substring(lastSlash + 1)); } @Override @@ -311,7 +224,6 @@ private ScmDetails getGitDetails(String jobName, Integer buildNumber) { false); } - // TODO(rz): Unused? public Build getLatestBuild(String jobName) { return new SimpleJava8HystrixCommand<>( groupKey, @@ -320,54 +232,22 @@ public Build getLatestBuild(String jobName) { .execute(); } - @Override - public QueuedJob getQueuedBuild(String queueId) { + public QueuedJob queuedBuild(Integer item) { try { - return jenkinsClient.getQueuedItem(Integer.valueOf(queueId)); + return jenkinsClient.getQueuedItem(item); } catch (RetrofitError e) { - if (e.getResponse() != null && e.getResponse().getStatus() == 404) { - throw new NotFoundException( - format("Queued job '%s' not found for master '%s'.", queueId, groupKey)); + if (e.getResponse() != null && e.getResponse().getStatus() == NOT_FOUND.value()) { + throw new NotFoundException("Queued job '${item}' not found for master '${master}'."); } throw e; } } - @Override - public void stopQueuedBuild(String jobName, String queueId, int buildNumber) { - String crumb = getCrumb(); - - // Jobs that haven't been started yet won't have a buildNumber - // (They're still in the queue). We use 0 to denote that case - if (buildNumber != 0) { - jenkinsClient.stopRunningBuild(encode(jobName), buildNumber, "", crumb); - } - - // The jenkins api for removing a job from the queue - // (http:///queue/cancelItem?id=) - // always returns a 404. This try catch block insures that the exception is eaten instead - // of being handled by the handleOtherException handler and returning a 500 to orca - try { - jenkinsClient.stopQueuedBuild(queueId, "", crumb); - return; - } catch (RetrofitError e) { - if (e.getResponse() != null && e.getResponse().getStatus() != NOT_FOUND.value()) { - throw e; - } - } - jenkinsClient.stopQueuedBuild(queueId, "", crumb); - } - - @Override - public void stopRunningBuild(String jobName, int buildNumber) { - jenkinsClient.stopRunningBuild(encode(jobName), buildNumber, "", getCrumb()); - } - - private Response build(String jobName) { + public Response build(String jobName) { return jenkinsClient.build(encode(jobName), "", getCrumb()); } - private Response buildWithParameters(String jobName, Map queryParams) { + public Response buildWithParameters(String jobName, Map queryParams) { return jenkinsClient.buildWithParameters(encode(jobName), queryParams, "", getCrumb()); } @@ -381,22 +261,22 @@ public Map getBuildProperties(String job, GenericBuild build, St if (StringUtils.isEmpty(fileName)) { return new HashMap<>(); } - + Map map = new HashMap<>(); try { String path = getArtifactPathFromBuild(job, build.getNumber(), fileName); try (InputStream propertyStream = this.getPropertyFile(job, build.getNumber(), path).getBody().in()) { if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) { Yaml yml = new Yaml(new SafeConstructor()); - return yml.load(propertyStream); + map = yml.load(propertyStream); } else if (fileName.endsWith(".json")) { - return objectMapper.readValue( - propertyStream, new TypeReference>() {}); + map = objectMapper.readValue(propertyStream, new TypeReference>() {}); } else { Properties properties = new Properties(); properties.load(propertyStream); - return properties.entrySet().stream() - .collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue)); + map = + properties.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue)); } } } catch (NotFoundException e) { @@ -404,8 +284,7 @@ public Map getBuildProperties(String job, GenericBuild build, St } catch (Exception e) { log.error("Unable to get igorProperties '{}'", kv("job", job), e); } - - return new HashMap<>(); + return map; } private String getArtifactPathFromBuild(String job, int buildNumber, String fileName) { @@ -450,6 +329,14 @@ private Response getPropertyFile(String jobName, Integer buildNumber, String fil false); } + public Response stopRunningBuild(String jobName, Integer buildNumber) { + return jenkinsClient.stopRunningBuild(encode(jobName), buildNumber, "", getCrumb()); + } + + public Response stopQueuedBuild(String queuedBuild) { + return jenkinsClient.stopQueuedBuild(queuedBuild, "", getCrumb()); + } + /** * A CommandKey should be unique per group (to ensure broken circuits do not span Jenkins masters) */ diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildServiceProvider.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/BuildServiceProvider.groovy similarity index 67% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildServiceProvider.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/BuildServiceProvider.groovy index b6dd193b5..e56f578b6 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildServiceProvider.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/BuildServiceProvider.groovy @@ -1,11 +1,12 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2016 Schibsted ASA. + * Copyright (c) 2017, 2018, Oracle Corporation and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,17 +15,13 @@ * limitations under the License. */ -package com.netflix.spinnaker.igor.service; +package com.netflix.spinnaker.igor.model -/** - * TODO(rz): Delete this enum entirely. Replace with a registry or something if it's actually - * needed. - */ -public enum BuildServiceProvider { +enum BuildServiceProvider { JENKINS, TRAVIS, CONCOURSE, GITLAB_CI, WERCKER, - GCB; + GCB } diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/Crumb.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/Crumb.groovy new file mode 100644 index 000000000..f874b837d --- /dev/null +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/model/Crumb.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.igor.model + + +import javax.xml.bind.annotation.XmlElement +import javax.xml.bind.annotation.XmlRootElement + +/** + * Represents a Jenkins CSRF Crumb. + */ +@XmlRootElement +class Crumb { + @XmlElement + String crumbRequestField + + @XmlElement + String crumb +} diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildOperations.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildOperations.java similarity index 89% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildOperations.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildOperations.java index 7902aace0..881230438 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildOperations.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildOperations.java @@ -1,11 +1,12 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2019 Schibsted ASA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -63,8 +64,4 @@ public interface BuildOperations extends BuildService { List getBuilds(String job); JobConfiguration getJobConfig(String jobName); - - default void stopRunningBuild(String jobName, int buildNumber) { - throw new UnsupportedOperationException("build service has not implemented build stopping yet"); - } } diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildProperties.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildProperties.java similarity index 100% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildProperties.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildProperties.java diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildService.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildService.java similarity index 94% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildService.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildService.java index 2dc43fd85..c0d95e105 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildService.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildService.java @@ -1,11 +1,11 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2016 Schibsted ASA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.netflix.spinnaker.fiat.model.resources.Permissions; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; /** * Interface representing a Build Service host (CI) and the permissions needed to access it. Most diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildServices.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildServices.java similarity index 91% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildServices.java rename to igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildServices.java index 8684a007d..c88196820 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/service/BuildServices.java +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/service/BuildServices.java @@ -1,11 +1,11 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2016 Schibsted ASA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,6 +16,7 @@ package com.netflix.spinnaker.igor.service; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildContent.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildContent.java deleted file mode 100644 index 892ce0263..000000000 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildContent.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.wercker; - -import com.netflix.spinnaker.igor.build.model.GenericProject; -import com.netflix.spinnaker.igor.history.model.BuildContent; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class WerckerBuildContent implements BuildContent { - private String master; - private GenericProject project; -} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildEvent.java b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildEvent.java deleted file mode 100644 index 9ddd3ecb3..000000000 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.wercker; - -import com.netflix.spinnaker.igor.history.model.BuildEvent; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class WerckerBuildEvent implements BuildEvent { - private WerckerBuildContent content; -} diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitor.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitor.groovy index be8f3b518..b09c2458b 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitor.groovy +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitor.groovy @@ -22,9 +22,9 @@ import com.netflix.spinnaker.igor.build.model.GenericProject import com.netflix.spinnaker.igor.build.model.Result import com.netflix.spinnaker.igor.config.WerckerProperties import com.netflix.spinnaker.igor.history.EchoService -import com.netflix.spinnaker.igor.history.model.EmptyBuildContent +import com.netflix.spinnaker.igor.history.model.GenericBuildContent import com.netflix.spinnaker.igor.history.model.GenericBuildEvent -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.polling.CommonPollingMonitor import com.netflix.spinnaker.igor.polling.DeltaItem import com.netflix.spinnaker.igor.polling.LockService @@ -212,15 +212,15 @@ class WerckerBuildMonitor extends CommonPollingMonitor { diff --git a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerService.groovy b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerService.groovy index 65e36048a..9266a397f 100644 --- a/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerService.groovy +++ b/igor-web/src/main/groovy/com/netflix/spinnaker/igor/wercker/WerckerService.groovy @@ -10,6 +10,7 @@ package com.netflix.spinnaker.igor.wercker import com.netflix.spinnaker.fiat.model.resources.Permissions import com.netflix.spinnaker.hystrix.SimpleHystrixCommand +import com.netflix.spinnaker.igor.build.BuildController import com.netflix.spinnaker.igor.build.model.GenericBuild import com.netflix.spinnaker.igor.build.model.GenericGitRevision import com.netflix.spinnaker.igor.build.model.GenericJobConfiguration @@ -17,25 +18,23 @@ import com.netflix.spinnaker.igor.build.model.JobConfiguration import com.netflix.spinnaker.igor.build.model.Result import com.netflix.spinnaker.igor.config.WerckerProperties.WerckerHost import com.netflix.spinnaker.igor.exceptions.BuildJobError -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.service.BuildOperations -import com.netflix.spinnaker.igor.service.JobNamesProvider import com.netflix.spinnaker.igor.wercker.model.Application import com.netflix.spinnaker.igor.wercker.model.Pipeline import com.netflix.spinnaker.igor.wercker.model.QualifiedPipelineName import com.netflix.spinnaker.igor.wercker.model.Run import com.netflix.spinnaker.igor.wercker.model.RunPayload -import com.netflix.spinnaker.kork.web.exceptions.InvalidRequestException import groovy.util.logging.Slf4j import retrofit.RetrofitError import retrofit.client.Response import retrofit.mime.TypedByteArray -import static com.netflix.spinnaker.igor.service.BuildServiceProvider.WERCKER +import static com.netflix.spinnaker.igor.model.BuildServiceProvider.WERCKER import static net.logstash.logback.argument.StructuredArguments.kv @Slf4j -class WerckerService implements BuildOperations, JobNamesProvider { +class WerckerService implements BuildOperations { String groupKey WerckerClient werckerClient @@ -97,7 +96,7 @@ class WerckerService implements BuildOperations, JobNamesProvider { Run run = getRunById(runId) String addr = address.endsWith("/") ? address.substring(0, address.length()-1) : address - GenericBuild genericBuild = GenericBuild.builder().build() + GenericBuild genericBuild = new GenericBuild() genericBuild.name = job genericBuild.building = true genericBuild.fullDisplayName = "Wercker Job " + job + " [" + buildNumber + "]" @@ -186,7 +185,7 @@ class WerckerService implements BuildOperations, JobNamesProvider { "Failed to trigger build for pipeline ${pipelineName}! Error from Wercker is: ${wkrMsg}") } } else { - throw new InvalidJobParameterException( + throw new BuildController.InvalidJobParameterException( "Could not retrieve pipeline ${pipelineName} for application ${appName} from Wercker!") } } @@ -355,15 +354,4 @@ class WerckerService implements BuildOperations, JobNamesProvider { JobConfiguration getJobConfig(String jobName) { return new GenericJobConfiguration('WerckerPipeline ' + jobName, jobName) } - - @Override - List getJobNames() { - return getJobs(); - } - - class InvalidJobParameterException extends InvalidRequestException { - InvalidJobParameterException(String message) { - super(message) - } - } } diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildContent.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildContent.java deleted file mode 100644 index 0b465610b..000000000 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildContent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.concourse; - -import com.netflix.spinnaker.igor.build.model.GenericProject; -import com.netflix.spinnaker.igor.history.model.BuildContent; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class ConcourseBuildContent implements BuildContent { - - public static final String TYPE = "concourse"; - - private GenericProject project; - private String master; - - public void setMaster(String master) { - this.master = "concourse-" + master; - } - - @Override - public String getType() { - return TYPE; - } -} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildEvent.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildEvent.java deleted file mode 100644 index b9febff53..000000000 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.concourse; - -import com.netflix.spinnaker.igor.history.model.BuildEvent; -import lombok.Data; - -@Data -public class ConcourseBuildEvent implements BuildEvent { - private ConcourseBuildContent content; - - public ConcourseBuildEvent(ConcourseBuildContent content) { - this.content = content; - } -} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildMonitor.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildMonitor.java index 70f430fcf..c8f1a9ebf 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildMonitor.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/ConcourseBuildMonitor.java @@ -26,6 +26,8 @@ import com.netflix.spinnaker.igor.concourse.service.ConcourseService; import com.netflix.spinnaker.igor.config.ConcourseProperties; import com.netflix.spinnaker.igor.history.EchoService; +import com.netflix.spinnaker.igor.history.model.GenericBuildContent; +import com.netflix.spinnaker.igor.history.model.GenericBuildEvent; import com.netflix.spinnaker.igor.polling.*; import com.netflix.spinnaker.igor.service.BuildServices; import com.netflix.spinnaker.security.AuthenticatedRequest; @@ -181,10 +183,15 @@ private void sendEventForBuild(ConcourseProperties.Host host, Job job, GenericBu new GenericProject( job.getTeamName() + "/" + job.getPipelineName() + "/" + job.getName(), build); - ConcourseBuildContent content = new ConcourseBuildContent(project, host.getName()); + GenericBuildContent content = new GenericBuildContent(); + content.setProject(project); + content.setMaster("concourse-" + host.getName()); + content.setType("concourse"); - AuthenticatedRequest.allowAnonymous( - () -> echoService.get().postEvent(new ConcourseBuildEvent(content))); + GenericBuildEvent event = new GenericBuildEvent(); + event.setContent(content); + + AuthenticatedRequest.allowAnonymous(() -> echoService.get().postEvent(event)); } else { log.warn("Cannot send build event notification: Echo is not configured"); log.info("({}) unable to push event for :" + build.getFullDisplayName()); diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/service/ConcourseService.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/service/ConcourseService.java index 74010cb17..9475f7947 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/service/ConcourseService.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/concourse/service/ConcourseService.java @@ -36,10 +36,10 @@ import com.netflix.spinnaker.igor.concourse.client.model.Resource; import com.netflix.spinnaker.igor.concourse.client.model.Team; import com.netflix.spinnaker.igor.config.ConcourseProperties; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; import com.netflix.spinnaker.igor.service.ArtifactDecorator; import com.netflix.spinnaker.igor.service.BuildOperations; import com.netflix.spinnaker.igor.service.BuildProperties; -import com.netflix.spinnaker.igor.service.BuildServiceProvider; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; @@ -144,7 +144,7 @@ public GenericBuild getGenericBuild(String jobPath, int buildNumber) { public GenericBuild getGenericBuild(String jobPath, Build b, boolean fetchResources) { Job job = toJob(jobPath); - GenericBuild build = GenericBuild.builder().build(); + GenericBuild build = new GenericBuild(); build.setId(b.getId()); build.setBuilding(false); build.setNumber(b.getNumber()); diff --git a/igor-core/src/main/java/com/netflix/spinnaker/igor/config/BuildServerProperties.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/BuildServerProperties.java similarity index 97% rename from igor-core/src/main/java/com/netflix/spinnaker/igor/config/BuildServerProperties.java rename to igor-web/src/main/java/com/netflix/spinnaker/igor/config/BuildServerProperties.java index 2d2519ccd..21cedeb1a 100644 --- a/igor-core/src/main/java/com/netflix/spinnaker/igor/config/BuildServerProperties.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/BuildServerProperties.java @@ -1,11 +1,12 @@ /* - * Copyright 2020 Netflix, Inc. + * Copyright 2019 Schibsted ASA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java index 4b363db6c..a0075939f 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/GoogleCloudBuildProperties.java @@ -17,8 +17,8 @@ package com.netflix.spinnaker.igor.config; import com.netflix.spinnaker.fiat.model.resources.Permissions; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; import com.netflix.spinnaker.igor.service.BuildService; -import com.netflix.spinnaker.igor.service.BuildServiceProvider; import java.util.List; import java.util.stream.Collectors; import lombok.Data; diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/health/EchoServiceHealthIndicator.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/health/EchoServiceHealthIndicator.java index d0a462bd2..a4c43f823 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/health/EchoServiceHealthIndicator.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/health/EchoServiceHealthIndicator.java @@ -18,8 +18,11 @@ import com.netflix.spectator.api.Registry; import com.netflix.spectator.api.patterns.PolledMeter; +import com.netflix.spinnaker.igor.build.model.GenericBuild; +import com.netflix.spinnaker.igor.build.model.GenericProject; import com.netflix.spinnaker.igor.history.EchoService; -import com.netflix.spinnaker.igor.history.model.Event; +import com.netflix.spinnaker.igor.history.model.GenericBuildContent; +import com.netflix.spinnaker.igor.history.model.GenericBuildEvent; import com.netflix.spinnaker.security.AuthenticatedRequest; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -39,15 +42,14 @@ @Component @ConditionalOnBean(EchoService.class) public class EchoServiceHealthIndicator implements HealthIndicator { - - private static final Event EVENT = new HealthCheckEvent(); - private Logger log = LoggerFactory.getLogger(EchoServiceHealthIndicator.class); private final AtomicReference lastException = new AtomicReference<>(null); private final AtomicBoolean upOnce; private final Optional echoService; private final AtomicLong errors; + static final GenericBuildEvent event = buildGenericEvent(); + @Autowired EchoServiceHealthIndicator(Registry registry, Optional echoService) { this.echoService = echoService; @@ -76,7 +78,7 @@ void checkHealth() { echoService.ifPresent( s -> { try { - AuthenticatedRequest.allowAnonymous(() -> s.postEvent(EVENT)); + AuthenticatedRequest.allowAnonymous(() -> s.postEvent(event)); upOnce.set(true); errors.set(0); lastException.set(null); @@ -88,13 +90,20 @@ void checkHealth() { }); } + private static GenericBuildEvent buildGenericEvent() { + final GenericBuildEvent event = new GenericBuildEvent(); + final GenericBuildContent buildContent = new GenericBuildContent(); + final GenericProject project = new GenericProject("spinnaker", new GenericBuild()); + buildContent.setMaster("IgorHealthCheck"); + buildContent.setProject(project); + event.setContent(buildContent); + return event; + } + @ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE, reason = "Could not reach Echo.") static class EchoUnreachableException extends RuntimeException { public EchoUnreachableException(Throwable cause) { super(cause); } } - - /** No need to send anything across the wire. */ - private static class HealthCheckEvent implements Event {} } diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/nexus/model/NexusAssetEvent.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/nexus/model/NexusAssetEvent.java index 4f7ac58f1..778c8c68e 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/nexus/model/NexusAssetEvent.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/nexus/model/NexusAssetEvent.java @@ -28,7 +28,7 @@ @RequiredArgsConstructor @Data @EqualsAndHashCode(callSuper = false) -public class NexusAssetEvent implements Event { +public class NexusAssetEvent extends Event { private final Content content; private final Map details = ImmutableMap.builder().put("type", "nexus").put("source", "igor").build(); diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildContent.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildContent.java deleted file mode 100644 index 16332e167..000000000 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildContent.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.travis; - -import com.netflix.spinnaker.igor.build.model.GenericProject; -import com.netflix.spinnaker.igor.history.model.BuildContent; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class TravisBuildContent implements BuildContent { - public static final String TYPE = "travis"; - - private GenericProject project; - private String master; - - @Override - public String getType() { - return TYPE; - } -} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildEvent.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildEvent.java deleted file mode 100644 index 1740ade3b..000000000 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2020 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.igor.travis; - -import com.netflix.spinnaker.igor.history.model.BuildEvent; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class TravisBuildEvent implements BuildEvent { - private TravisBuildContent content; -} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildMonitor.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildMonitor.java index 0f6becb1d..dcf913bb0 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildMonitor.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/TravisBuildMonitor.java @@ -27,12 +27,14 @@ import com.netflix.spinnaker.igor.build.model.GenericProject; import com.netflix.spinnaker.igor.config.TravisProperties; import com.netflix.spinnaker.igor.history.EchoService; +import com.netflix.spinnaker.igor.history.model.GenericBuildContent; +import com.netflix.spinnaker.igor.history.model.GenericBuildEvent; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; import com.netflix.spinnaker.igor.polling.CommonPollingMonitor; import com.netflix.spinnaker.igor.polling.DeltaItem; import com.netflix.spinnaker.igor.polling.LockService; import com.netflix.spinnaker.igor.polling.PollContext; import com.netflix.spinnaker.igor.polling.PollingDelta; -import com.netflix.spinnaker.igor.service.BuildServiceProvider; import com.netflix.spinnaker.igor.service.BuildServices; import com.netflix.spinnaker.igor.travis.client.model.v3.TravisBuildState; import com.netflix.spinnaker.igor.travis.client.model.v3.V3Build; @@ -247,10 +249,16 @@ private void sendEventForBuild( kv("master", master)); GenericProject project = new GenericProject(branchedSlug, buildDelta.getGenericBuild()); - TravisBuildContent content = new TravisBuildContent(project, master); - AuthenticatedRequest.allowAnonymous( - () -> echoService.get().postEvent(new TravisBuildEvent(content))); + GenericBuildContent content = new GenericBuildContent(); + content.setProject(project); + content.setMaster(master); + content.setType("travis"); + + GenericBuildEvent event = new GenericBuildEvent(); + event.setContent(content); + + AuthenticatedRequest.allowAnonymous(() -> echoService.get().postEvent(event)); } else { log.warn("Cannot send build event notification: Echo is not configured"); log.info( diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisBuildConverter.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisBuildConverter.java index c50f5e724..2d3196452 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisBuildConverter.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisBuildConverter.java @@ -24,16 +24,14 @@ public class TravisBuildConverter { public static GenericBuild genericBuild(Build build, String repoSlug, String baseUrl) { - GenericBuild genericBuild = - GenericBuild.builder() - .building(build.getState() == TravisBuildState.started) - .number(build.getNumber()) - .duration(build.getDuration()) - .result(build.getState().getResult()) - .name(repoSlug) - .url(url(repoSlug, baseUrl, build.getId())) - .id(String.valueOf(build.getId())) - .build(); + GenericBuild genericBuild = new GenericBuild(); + genericBuild.setBuilding(build.getState() == TravisBuildState.started); + genericBuild.setNumber(build.getNumber()); + genericBuild.setDuration(build.getDuration()); + genericBuild.setResult(build.getState().getResult()); + genericBuild.setName(repoSlug); + genericBuild.setUrl(url(repoSlug, baseUrl, build.getId())); + genericBuild.setId(String.valueOf(build.getId())); if (build.getFinishedAt() != null) { genericBuild.setTimestamp(String.valueOf(build.getTimestamp())); } @@ -41,15 +39,14 @@ public static GenericBuild genericBuild(Build build, String repoSlug, String bas } public static GenericBuild genericBuild(V3Build build, String baseUrl) { - GenericBuild genericBuild = - GenericBuild.builder() - .building(build.getState() == TravisBuildState.started) - .number(build.getNumber()) - .result(build.getState().getResult()) - .name(build.getRepository().getSlug()) - .url(url(build.getRepository().getSlug(), baseUrl, build.getId())) - .id(String.valueOf(build.getId())) - .build(); + GenericBuild genericBuild = new GenericBuild(); + genericBuild.setBuilding(build.getState() == TravisBuildState.started); + genericBuild.setNumber(build.getNumber()); + genericBuild.setDuration(build.getDuration()); + genericBuild.setResult(build.getState().getResult()); + genericBuild.setName(build.getRepository().getSlug()); + genericBuild.setUrl(url(build.getRepository().getSlug(), baseUrl, build.getId())); + genericBuild.setId(String.valueOf(build.getId())); if (build.getFinishedAt() != null) { genericBuild.setTimestamp(String.valueOf(build.getTimestamp())); } diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisService.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisService.java index 182f2743f..ba3007641 100644 --- a/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisService.java +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/travis/service/TravisService.java @@ -26,7 +26,10 @@ import com.netflix.spinnaker.igor.build.model.GenericJobConfiguration; import com.netflix.spinnaker.igor.build.model.JobConfiguration; import com.netflix.spinnaker.igor.build.model.Result; -import com.netflix.spinnaker.igor.service.*; +import com.netflix.spinnaker.igor.model.BuildServiceProvider; +import com.netflix.spinnaker.igor.service.ArtifactDecorator; +import com.netflix.spinnaker.igor.service.BuildOperations; +import com.netflix.spinnaker.igor.service.BuildProperties; import com.netflix.spinnaker.igor.travis.TravisCache; import com.netflix.spinnaker.igor.travis.client.TravisClient; import com.netflix.spinnaker.igor.travis.client.logparser.ArtifactParser; @@ -66,9 +69,7 @@ import org.slf4j.LoggerFactory; import retrofit.RetrofitError; -/** TODO(rz): The generic type of QueuingSupport isn't very good. */ -public class TravisService - implements BuildOperations, BuildProperties, BuildQueueOperations> { +public class TravisService implements BuildOperations, BuildProperties { static final int TRAVIS_BUILD_RESULT_LIMIT = 100; @@ -493,9 +494,8 @@ public String getUrl(String repoSlug) { return baseUrl + "/" + repoSlug; } - @Override - public Map getQueuedBuild(String queueId) { - Map queuedJob = travisCache.getQueuedJob(groupKey, Integer.parseInt(queueId)); + public Map queuedBuild(int queueId) { + Map queuedJob = travisCache.getQueuedJob(groupKey, queueId); Request requestResponse = travisClient.request( getAccessToken(), queuedJob.get("repositoryId"), queuedJob.get("requestId")); @@ -507,7 +507,7 @@ public Map getQueuedBuild(String queueId) { requestResponse.getBuilds().get(0).getNumber(), queueId, groupKey); - travisCache.removeQuededJob(groupKey, Integer.parseInt(queueId)); + travisCache.removeQuededJob(groupKey, queueId); LinkedHashMap map = new LinkedHashMap<>(1); map.put("number", requestResponse.getBuilds().get(0).getNumber()); return map; diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractorSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractorSpec.groovy index 8c5b3314a..f32982451 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractorSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/artifacts/ArtifactExtractorSpec.groovy @@ -40,7 +40,8 @@ class ArtifactExtractorSpec extends Specification { messageFormat: 'JAR', customFormat: 'false' ] - def build = GenericBuild.builder().properties(properties).build() + def build = new GenericBuild() + build.properties = properties when: def artifacts = artifactExtractor.extractArtifacts(build) diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/BuildControllerSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/BuildControllerSpec.groovy index ff9081ca1..1f22832bb 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/BuildControllerSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/BuildControllerSpec.groovy @@ -18,27 +18,34 @@ package com.netflix.spinnaker.igor.build import com.netflix.spinnaker.igor.build.model.GenericBuild import com.netflix.spinnaker.igor.config.JenkinsConfig -import com.netflix.spinnaker.igor.jenkins.JenkinsService import com.netflix.spinnaker.igor.jenkins.client.model.Build import com.netflix.spinnaker.igor.jenkins.client.model.BuildArtifact +import com.netflix.spinnaker.igor.jenkins.client.model.JobConfig +import com.netflix.spinnaker.igor.jenkins.client.model.ParameterDefinition import com.netflix.spinnaker.igor.jenkins.client.model.QueuedJob -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.service.BuildOperations import com.netflix.spinnaker.igor.service.BuildServices import com.netflix.spinnaker.igor.travis.service.TravisService +import com.netflix.spinnaker.kork.core.RetrySupport import com.netflix.spinnaker.kork.web.exceptions.GenericExceptionHandlers import com.netflix.spinnaker.kork.web.exceptions.NotFoundException import com.squareup.okhttp.mockwebserver.MockWebServer +import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.mock.web.MockHttpServletResponse import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.setup.MockMvcBuilders +import retrofit.client.Header +import retrofit.client.Response import spock.lang.Shared import spock.lang.Specification +import static com.netflix.spinnaker.igor.build.BuildController.* import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status - /** * Tests for BuildController */ @@ -51,6 +58,10 @@ class BuildControllerSpec extends Specification { JenkinsService jenkinsService BuildOperations service TravisService travisService + Map serviceList + def retrySupport = Spy(RetrySupport) { + _ * sleep(_) >> { /* do nothing */ } + } @Shared MockWebServer server @@ -58,10 +69,12 @@ class BuildControllerSpec extends Specification { final SERVICE = 'SERVICE' final JENKINS_SERVICE = 'JENKINS_SERVICE' final TRAVIS_SERVICE = 'TRAVIS_SERVICE' + final HTTP_201 = 201 final BUILD_NUMBER = 123 final BUILD_ID = 654321 final QUEUED_JOB_NUMBER = 123456 final JOB_NAME = "job/name/can/have/slashes" + final JOB_NAME_LEGACY = "job" final FILE_NAME = "test.yml" GenericBuild genericBuild @@ -82,7 +95,7 @@ class BuildControllerSpec extends Specification { (JENKINS_SERVICE): jenkinsService, (TRAVIS_SERVICE): travisService, ]) - genericBuild = GenericBuild.builder().build() + genericBuild = new GenericBuild() genericBuild.number = BUILD_NUMBER genericBuild.id = BUILD_ID @@ -97,7 +110,7 @@ class BuildControllerSpec extends Specification { void 'get the status of a build'() { given: - 1 * service.getGenericBuild(JOB_NAME, BUILD_NUMBER) >> GenericBuild.builder().building(false).number(BUILD_NUMBER).build() + 1 * service.getGenericBuild(JOB_NAME, BUILD_NUMBER) >> new GenericBuild(building: false, number: BUILD_NUMBER) when: MockHttpServletResponse response = mockMvc.perform(get("/builds/status/${BUILD_NUMBER}/${SERVICE}/${JOB_NAME}") @@ -109,7 +122,7 @@ class BuildControllerSpec extends Specification { void 'get an item from the queue'() { given: - 1 * jenkinsService.getQueuedBuild(QUEUED_JOB_NUMBER.toString()) >> new QueuedJob(executable: [number: QUEUED_JOB_NUMBER]) + 1 * jenkinsService.queuedBuild(QUEUED_JOB_NUMBER) >> new QueuedJob(executable: [number: QUEUED_JOB_NUMBER]) when: MockHttpServletResponse response = mockMvc.perform(get("/builds/queue/${JENKINS_SERVICE}/${QUEUED_JOB_NUMBER}") @@ -214,4 +227,123 @@ class BuildControllerSpec extends Specification { then: response.contentAsString == "{\"foo\":\"bar\"}" } + + void 'trigger a build without parameters'() { + given: + 1 * jenkinsService.getJobConfig(JOB_NAME) >> new JobConfig(buildable: true) + 1 * jenkinsService.build(JOB_NAME) >> new Response("http://test.com", HTTP_201, "", [new Header("Location", "foo/${BUILD_NUMBER}")], null) + + when: + MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${JOB_NAME}") + .accept(MediaType.APPLICATION_JSON)).andReturn().response + + then: + response.contentAsString == BUILD_NUMBER.toString() + + } + + void 'trigger a build with parameters to a job with parameters'() { + given: + 1 * jenkinsService.getJobConfig(JOB_NAME) >> new JobConfig(buildable: true, parameterDefinitionList: [new ParameterDefinition(defaultParameterValue: [name: "name", value: null], description: "description")]) + 1 * jenkinsService.buildWithParameters(JOB_NAME, [name: "myName"]) >> new Response("http://test.com", HTTP_201, "", [new Header("Location", "foo/${BUILD_NUMBER}")], null) + + when: + MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${JOB_NAME}") + .contentType(MediaType.APPLICATION_JSON).param("name", "myName")).andReturn().response + + then: + response.contentAsString == BUILD_NUMBER.toString() + } + + void 'trigger a build without parameters to a job with parameters with default values'() { + given: + 1 * jenkinsService.getJobConfig(JOB_NAME) >> new JobConfig(buildable: true, parameterDefinitionList: [new ParameterDefinition(defaultParameterValue: [name: "name", value: "value"], description: "description")]) + 1 * jenkinsService.buildWithParameters(JOB_NAME, ['startedBy': "igor"]) >> new Response("http://test.com", HTTP_201, "", [new Header("Location", "foo/${BUILD_NUMBER}")], null) + + when: + MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${JOB_NAME}", "") + .accept(MediaType.APPLICATION_JSON)).andReturn().response + + then: + response.contentAsString == BUILD_NUMBER.toString() + } + + void 'trigger a build with parameters to a job without parameters'() { + given: + 1 * jenkinsService.getJobConfig(JOB_NAME) >> new JobConfig(buildable: true) + + when: + MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${JOB_NAME}") + .contentType(MediaType.APPLICATION_JSON).param("foo", "bar")).andReturn().response + + then: + response.status == HttpStatus.INTERNAL_SERVER_ERROR.value() + } + + void 'trigger a build with an invalid choice'() { + given: + JobConfig config = new JobConfig(buildable: true) + config.parameterDefinitionList = [ + new ParameterDefinition(type: "ChoiceParameterDefinition", name: "foo", choices: ["bar", "baz"]) + ] + 1 * jenkinsService.getJobConfig(JOB_NAME) >> config + + when: + MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${JOB_NAME}") + .contentType(MediaType.APPLICATION_JSON).param("foo", "bat")).andReturn().response + + then: + + response.status == HttpStatus.BAD_REQUEST.value() + response.errorMessage == "`bat` is not a valid choice for `foo`. Valid choices are: bar, baz" + } + + void 'trigger a disabled build'() { + given: + JobConfig config = new JobConfig() + 1 * jenkinsService.getJobConfig(JOB_NAME) >> config + + when: + MockHttpServletResponse response = mockMvc.perform(put("/masters/${JENKINS_SERVICE}/jobs/${JOB_NAME}") + .contentType(MediaType.APPLICATION_JSON).param("foo", "bat")).andReturn().response + + then: + response.status == HttpStatus.BAD_REQUEST.value() + response.errorMessage == "Job '${JOB_NAME}' is not buildable. It may be disabled." + } + + void 'validation successful for null list of choices'() { + given: + Map requestParams = ["hey" : "you"] + ParameterDefinition parameterDefinition = new ParameterDefinition() + parameterDefinition.choices = null + parameterDefinition.type = "ChoiceParameterDefinition" + parameterDefinition.name = "hey" + JobConfig jobConfig = new JobConfig() + jobConfig.parameterDefinitionList = [parameterDefinition] + + when: + validateJobParameters(jobConfig, requestParams) + + then: + noExceptionThrown() + } + + void 'validation failed for option not in list of choices'() { + given: + Map requestParams = ["hey" : "you"] + ParameterDefinition parameterDefinition = new ParameterDefinition() + parameterDefinition.choices = ["why", "not"] + parameterDefinition.type = "ChoiceParameterDefinition" + parameterDefinition.name = "hey" + JobConfig jobConfig = new JobConfig() + jobConfig.parameterDefinitionList = [parameterDefinition] + + when: + validateJobParameters(jobConfig, requestParams) + + then: + thrown(InvalidJobParameterException) + } + } diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy index 9f52c9213..8035e5bce 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/build/InfoControllerSpec.groovy @@ -21,8 +21,8 @@ import com.netflix.spinnaker.fiat.model.resources.Permissions import com.netflix.spinnaker.igor.config.GoogleCloudBuildProperties import com.netflix.spinnaker.igor.config.JenkinsConfig import com.netflix.spinnaker.igor.config.JenkinsProperties -import com.netflix.spinnaker.igor.jenkins.JenkinsService -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.service.BuildOperations import com.netflix.spinnaker.igor.service.BuildServices import com.netflix.spinnaker.igor.travis.service.TravisService @@ -39,7 +39,6 @@ import spock.lang.Specification import spock.lang.Unroll import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get - /** * tests for the info controller */ @@ -241,14 +240,35 @@ class InfoControllerSpec extends Specification { .accept(MediaType.APPLICATION_JSON)).andReturn().response then: - jenkinsService.getJobNames() >> [ - 'job1', - 'job2', - 'job3' - ] + jenkinsService.getJobs() >> ['list': [ + ['name': 'job1'], + ['name': 'job2'], + ['name': 'job3'] + ]] response.contentAsString == '["job1","job2","job3"]' } + void 'is able to get jobs for a jenkins master with the folders plugin'() { + given: + JenkinsService jenkinsService = Stub(JenkinsService) + createMocks(['master1': jenkinsService]) + + when: + MockHttpServletResponse response = mockMvc.perform(get('/jobs/master1/') + .accept(MediaType.APPLICATION_JSON)).andReturn().response + + then: + jenkinsService.getBuildServiceProvider() >> BuildServiceProvider.JENKINS + jenkinsService.getJobs() >> ['list': [ + ['name': 'folder', 'list': [ + ['name': 'job1'], + ['name': 'job2'] + ] ], + ['name': 'job3'] + ]] + response.contentAsString == '["folder/job/job1","folder/job/job2","job3"]' + } + void 'is able to get jobs for a travis master'() { given: TravisService travisService = Stub(TravisService) @@ -277,8 +297,9 @@ class InfoControllerSpec extends Specification { then: werckerService.getBuildServiceProvider() >> BuildServiceProvider.WERCKER - werckerService.getJobNames() >> [werckerJob] + werckerService.getJobs() >> [werckerJob] response.contentAsString == '["' + werckerJob + '"]' + } private void setResponse(String body) { diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSchedulingSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSchedulingSpec.groovy index c4bcb11b9..62f472b7f 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSchedulingSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSchedulingSpec.groovy @@ -20,7 +20,8 @@ import com.netflix.spectator.api.NoopRegistry import com.netflix.spinnaker.igor.IgorConfigurationProperties import com.netflix.spinnaker.igor.config.JenkinsProperties import com.netflix.spinnaker.igor.jenkins.client.model.ProjectsList -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.service.BuildServices import com.netflix.spinnaker.kork.eureka.RemoteStatusChangedEvent import rx.schedulers.TestScheduler diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSpec.groovy index 3977ff950..7294d6236 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsBuildMonitorSpec.groovy @@ -24,6 +24,7 @@ import com.netflix.spinnaker.igor.history.model.Event import com.netflix.spinnaker.igor.jenkins.client.model.Build import com.netflix.spinnaker.igor.jenkins.client.model.Project import com.netflix.spinnaker.igor.jenkins.client.model.ProjectsList +import com.netflix.spinnaker.igor.jenkins.service.JenkinsService import com.netflix.spinnaker.igor.polling.PollContext import com.netflix.spinnaker.igor.service.BuildServices import org.slf4j.Logger @@ -56,9 +57,7 @@ class JenkinsBuildMonitorSpec extends Specification { buildServices, true, Optional.of(echoService), - new JenkinsProperties(masters: [ - new JenkinsProperties.JenkinsHost(name: MASTER, address: "http://example.net") - ]) + new JenkinsProperties() ) monitor.worker = Schedulers.immediate().createWorker() @@ -262,13 +261,18 @@ class JenkinsBuildMonitorSpec extends Specification { new Build(number: 3, timestamp: nowMinus30min, building: false, result: 'SUCCESS', duration: durationOf1min) ] + and: + monitor.log = Mock(Logger); + when: monitor.pollSingle(new PollContext(MASTER)) then: 'Builds are processed for job1' 1 * echoService.postEvent({ it.content.project.name == 'job1'} as Event) - and: 'no builds are processed for job2 due to errors' + and: 'Errors are logged for job2; no builds are processed' + 1 * monitor.log.error('Error communicating with jenkins for [{}:{}]: {}', _) + 1 * monitor.log.error('Error processing builds for [{}:{}]', _) 0 * echoService.postEvent({ it.content.project.name == 'job2'} as Event) and: 'Builds are not processed for job3' diff --git a/igor-monitor-jenkins/src/test/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClientSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClientSpec.groovy similarity index 99% rename from igor-monitor-jenkins/src/test/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClientSpec.groovy rename to igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClientSpec.groovy index ffd3cb7f2..b377c7bf3 100644 --- a/igor-monitor-jenkins/src/test/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClientSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/client/JenkinsClientSpec.groovy @@ -20,10 +20,10 @@ import com.netflix.spinnaker.igor.config.JenkinsConfig import com.netflix.spinnaker.igor.config.JenkinsProperties import com.netflix.spinnaker.igor.jenkins.client.model.Build import com.netflix.spinnaker.igor.jenkins.client.model.BuildArtifact -import com.netflix.spinnaker.igor.jenkins.client.model.Crumb import com.netflix.spinnaker.igor.jenkins.client.model.JobConfig import com.netflix.spinnaker.igor.jenkins.client.model.Project import com.netflix.spinnaker.igor.jenkins.client.model.ProjectsList +import com.netflix.spinnaker.igor.model.Crumb import com.squareup.okhttp.mockwebserver.MockResponse import com.squareup.okhttp.mockwebserver.MockWebServer import spock.lang.Shared diff --git a/igor-monitor-jenkins/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsServiceSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/service/JenkinsServiceSpec.groovy similarity index 89% rename from igor-monitor-jenkins/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsServiceSpec.groovy rename to igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/service/JenkinsServiceSpec.groovy index fb2f10862..ecac2591b 100644 --- a/igor-monitor-jenkins/src/test/groovy/com/netflix/spinnaker/igor/jenkins/JenkinsServiceSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/jenkins/service/JenkinsServiceSpec.groovy @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,45 +14,30 @@ * limitations under the License. */ -package com.netflix.spinnaker.igor.jenkins +package com.netflix.spinnaker.igor.jenkins.service import com.netflix.spinnaker.fiat.model.resources.Permissions import com.netflix.spinnaker.igor.build.model.GenericBuild import com.netflix.spinnaker.igor.build.model.GenericGitRevision import com.netflix.spinnaker.igor.config.JenkinsConfig import com.netflix.spinnaker.igor.config.JenkinsProperties -import com.netflix.spinnaker.igor.jenkins.JenkinsService import com.netflix.spinnaker.igor.jenkins.client.JenkinsClient import com.netflix.spinnaker.igor.jenkins.client.model.Build import com.netflix.spinnaker.igor.jenkins.client.model.BuildArtifact import com.netflix.spinnaker.igor.jenkins.client.model.BuildsList -import com.netflix.spinnaker.igor.jenkins.client.model.Job -import com.netflix.spinnaker.igor.jenkins.client.model.JobConfig -import com.netflix.spinnaker.igor.jenkins.client.model.JobList -import com.netflix.spinnaker.igor.jenkins.client.model.ParameterDefinition import com.netflix.spinnaker.igor.jenkins.client.model.Project -import com.netflix.spinnaker.igor.jenkins.exceptions.InvalidJobParameterException +import com.netflix.spinnaker.kork.exceptions.SpinnakerException import com.squareup.okhttp.mockwebserver.MockResponse import com.squareup.okhttp.mockwebserver.MockWebServer import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.mock.web.MockHttpServletResponse import retrofit.RetrofitError -import retrofit.client.Header import retrofit.client.Response +import retrofit.mime.TypedInput import retrofit.mime.TypedString -import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put - @SuppressWarnings(['LineLength', 'DuplicateNumberLiteral']) class JenkinsServiceSpec extends Specification { static { @@ -161,7 +146,7 @@ class JenkinsServiceSpec extends Specification { 'build' | [] 'buildWithParameters' | [['key': 'value']] 'stopRunningBuild' | [1] - 'stopQueuedBuild' | ["hello darkness", 1] + 'stopQueuedBuild' | [] } void 'we can read crumbs'() { @@ -303,7 +288,8 @@ class JenkinsServiceSpec extends Specification { password: 'password') client = new JenkinsConfig().jenkinsClient(host) service = new JenkinsService('http://my.jenkins.net', client, false, Permissions.EMPTY) - def genericBuild = GenericBuild.builder().number(1).build() + def genericBuild = new GenericBuild() + genericBuild.number = 1 when: List genericGitRevision = service.getGenericGitRevisions('test', genericBuild) @@ -357,7 +343,8 @@ class JenkinsServiceSpec extends Specification { password: 'password') client = new JenkinsConfig().jenkinsClient(host) service = new JenkinsService('http://my.jenkins.net', client, false, Permissions.EMPTY) - def genericBuild = GenericBuild.builder().number(1).build() + def genericBuild = new GenericBuild() + genericBuild.number = 1 when: List genericGitRevision = service.getGenericGitRevisions('test', genericBuild) @@ -411,7 +398,8 @@ class JenkinsServiceSpec extends Specification { password: 'password') client = new JenkinsConfig().jenkinsClient(host) service = new JenkinsService('http://my.jenkins.net', client, false, Permissions.EMPTY) - def genericBuild = GenericBuild.builder().number(1).build() + def genericBuild = new GenericBuild() + genericBuild.number = 1 when: List genericGitRevision = service.getGenericGitRevisions('test', genericBuild) @@ -471,7 +459,8 @@ class JenkinsServiceSpec extends Specification { password: 'password') client = new JenkinsConfig().jenkinsClient(host) service = new JenkinsService('http://my.jenkins.net', client, false, Permissions.EMPTY) - def genericBuild = GenericBuild.builder().number(1).build() + def genericBuild = new GenericBuild() + genericBuild.number = 1 when: List genericGitRevision = service.getGenericGitRevisions('test', genericBuild) @@ -534,7 +523,8 @@ class JenkinsServiceSpec extends Specification { password: 'password') client = new JenkinsConfig().jenkinsClient(host) service = new JenkinsService('http://my.jenkins.net', client, false, Permissions.EMPTY) - def genericBuild = GenericBuild.builder().number(1).build() + def genericBuild = new GenericBuild() + genericBuild.number = 1 when: List genericGitRevision = service.getGenericGitRevisions('test', genericBuild) @@ -597,7 +587,8 @@ class JenkinsServiceSpec extends Specification { password: 'password') client = new JenkinsConfig().jenkinsClient(host) service = new JenkinsService('http://my.jenkins.net', client, false, Permissions.EMPTY) - def genericBuild = GenericBuild.builder().number(1).build() + def genericBuild = new GenericBuild() + genericBuild.number = 1 when: List genericGitRevision = service.getGenericGitRevisions('test', genericBuild) @@ -650,7 +641,8 @@ class JenkinsServiceSpec extends Specification { password: 'password') client = new JenkinsConfig().jenkinsClient(host) service = new JenkinsService('http://my.jenkins.net', client, false, Permissions.EMPTY) - def genericBuild = GenericBuild.builder().number(1).build() + def genericBuild = new GenericBuild() + genericBuild.number = 1 expect: service.getBuildProperties("PropertiesTest", genericBuild, "props$extension") == testCase.result @@ -759,54 +751,4 @@ class JenkinsServiceSpec extends Specification { properties == [:] } - - void 'get jobs for a jenkins master with the folders plugin'() { - when: - def result = service.getJobNames() - - then: - 1 * client.getJobs() >> new JobList(list: [ - new Job(name: 'folder', list: [ - new Job(name: 'job1'), - new Job(name: 'job2') - ]), - new Job(name: 'job3') - ]) - result == ["folder/job/job1", "folder/job/job2", "job3"] - } - - void 'validation successful for null list of choices'() { - given: - Map requestParams = ["hey" : "you"] - ParameterDefinition parameterDefinition = new ParameterDefinition() - parameterDefinition.choices = null - parameterDefinition.type = "ChoiceParameterDefinition" - parameterDefinition.name = "hey" - JobConfig jobConfig = new JobConfig() - jobConfig.parameterDefinitionList = [parameterDefinition] - - when: - JenkinsService.validateJobParameters(jobConfig, requestParams) - - then: - noExceptionThrown() - } - - void 'validation failed for option not in list of choices'() { - given: - Map requestParams = ["hey" : "you"] - ParameterDefinition parameterDefinition = new ParameterDefinition() - parameterDefinition.choices = ["why", "not"] - parameterDefinition.type = "ChoiceParameterDefinition" - parameterDefinition.name = "hey" - JobConfig jobConfig = new JobConfig() - jobConfig.parameterDefinitionList = [parameterDefinition] - - when: - JenkinsService.validateJobParameters(jobConfig, requestParams) - - then: - thrown(InvalidJobParameterException) - } - } diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSchedulingSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSchedulingSpec.groovy index 2e582fe0f..158fbc906 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSchedulingSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSchedulingSpec.groovy @@ -11,7 +11,7 @@ package com.netflix.spinnaker.igor.wercker import com.netflix.spectator.api.NoopRegistry import com.netflix.spinnaker.igor.IgorConfigurationProperties import com.netflix.spinnaker.igor.config.WerckerProperties -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.service.BuildServices import com.netflix.spinnaker.kork.eureka.RemoteStatusChangedEvent diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSpec.groovy index c7c3885ae..da765011c 100644 --- a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSpec.groovy +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/wercker/WerckerBuildMonitorSpec.groovy @@ -14,7 +14,7 @@ import com.netflix.spinnaker.igor.IgorConfigurationProperties import com.netflix.spinnaker.igor.config.WerckerProperties import com.netflix.spinnaker.igor.config.WerckerProperties.WerckerHost import com.netflix.spinnaker.igor.history.EchoService -import com.netflix.spinnaker.igor.service.BuildServiceProvider +import com.netflix.spinnaker.igor.model.BuildServiceProvider import com.netflix.spinnaker.igor.service.BuildServices import com.netflix.spinnaker.igor.wercker.model.Application import com.netflix.spinnaker.igor.wercker.model.Owner diff --git a/settings.gradle b/settings.gradle index 7e6b6317e..4bcbf1fd9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,7 +19,6 @@ rootProject.name='igor' include 'igor-bom', 'igor-core', 'igor-monitor-artifactory', - 'igor-monitor-jenkins', 'igor-web' def setBuildFile(project) {