001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017 MicroBean. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.helm.maven; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Objects; 024 025import java.util.concurrent.Callable; 026 027import hapi.release.TestRunOuterClass.TestRun; 028 029import hapi.services.tiller.Tiller.TestReleaseRequest; 030import hapi.services.tiller.Tiller.TestReleaseResponse; 031 032import org.apache.maven.plugin.MojoFailureException; 033 034import org.apache.maven.plugin.logging.Log; 035 036import org.apache.maven.plugins.annotations.Mojo; 037import org.apache.maven.plugins.annotations.Parameter; 038 039import org.microbean.helm.ReleaseManager; 040 041/** 042 * Runs tests against a Helm release. 043 * 044 * @author <a href="https://about.me/lairdnelson" 045 * target="_parent">Laird Nelson</a> 046 */ 047@Mojo(name = "test") 048public class TestReleaseMojo extends AbstractSingleReleaseMojo { 049 050 051 /* 052 * Instance fields. 053 */ 054 055 056 /** 057 * The timeout, in seconds, to use for Kubernetes operations; set to 058 * {@code 300} by default for parity with the {@code helm} command 059 * line program. 060 */ 061 @Parameter(defaultValue = "300") 062 private long timeout; // in seconds 063 064 /** 065 * Whether test Pods should be deleted after the test completes. 066 */ 067 @Parameter 068 private boolean cleanup; 069 070 /** 071 * A {@link List} of <a 072 * href="apidocs/org/microbean/helm/maven/ReleaseTestListener.html">{@code 073 * ReleaseTestListener}</a>s that will be notified of the test 074 * results. 075 */ 076 @Parameter(alias = "releaseTestListenersList") 077 private List<ReleaseTestListener> releaseTestListeners; 078 079 080 /* 081 * Constructors. 082 */ 083 084 085 /** 086 * Creates a new {@link TestReleaseMojo}. 087 */ 088 public TestReleaseMojo() { 089 super(); 090 } 091 092 093 /* 094 * Protected instance methods. 095 */ 096 097 098 /** 099 * {@inheritDoc} 100 * 101 * <p>This implementation {@linkplain 102 * ReleaseManager#test(hapi.services.tiller.Tiller.TestReleaseRequest) 103 * runs tests against a release}.</p> 104 */ 105 @Override 106 protected void execute(final Callable<ReleaseManager> releaseManagerCallable) throws Exception { 107 Objects.requireNonNull(releaseManagerCallable); 108 final Log log = this.getLog(); 109 assert log != null; 110 111 final TestReleaseRequest.Builder requestBuilder = TestReleaseRequest.newBuilder(); 112 assert requestBuilder != null; 113 114 requestBuilder.setCleanup(this.getCleanup()); 115 116 final String releaseName = this.getReleaseName(); 117 if (releaseName != null) { 118 requestBuilder.setName(releaseName); 119 } 120 121 requestBuilder.setTimeout(this.getTimeout()); 122 123 final ReleaseManager releaseManager = releaseManagerCallable.call(); 124 if (releaseManager == null) { 125 throw new IllegalStateException("releaseManagerCallable.call() == null"); 126 } 127 128 if (log.isInfoEnabled()) { 129 log.info("Testing release " + releaseName); 130 } 131 132 final Iterator<TestReleaseResponse> testReleaseResponses = releaseManager.test(requestBuilder.build()); 133 assert testReleaseResponses != null; 134 if (testReleaseResponses.hasNext()) { 135 final List<ReleaseTestListener> listeners = this.getReleaseTestListenersList(); 136 while (testReleaseResponses.hasNext()) { 137 final TestReleaseResponse response = testReleaseResponses.next(); 138 assert response != null; 139 if (listeners != null && !listeners.isEmpty()) { 140 final ReleaseTestEvent event = new ReleaseTestEvent(this, response); 141 for (final ReleaseTestListener listener : listeners) { 142 if (listener != null) { 143 listener.releaseTested(event); 144 } 145 } 146 } 147 final TestRun.Status status = response.getStatus(); 148 if (TestRun.Status.FAILURE.equals(status)) { 149 throw new MojoFailureException(response.getMsg()); 150 } 151 } 152 } 153 154 } 155 156 157 /* 158 * Public instance methods. 159 */ 160 161 /** 162 * Returns {@code true} if test Pods should be deleted after the 163 * tests complete. 164 * 165 * @return {@code true} if test Pods should be deleted after the 166 * tests complete; {@code false} otherwise 167 * 168 * @see #setCleanup(boolean) 169 */ 170 public boolean getCleanup() { 171 return this.cleanup; 172 } 173 174 /** 175 * Sets whether test Pods should be deleted after the 176 * tests complete. 177 * 178 * @param cleanup if {@code true}, test Pods will be deleted after 179 * the tests complete 180 * 181 * @see #getCleanup() 182 */ 183 public void setCleanup(final boolean cleanup) { 184 this.cleanup = cleanup; 185 } 186 187 /** 188 * Returns the timeout value, in seconds, for Kubernetes operations. 189 * 190 * @return the timeout value, in seconds, for Kubernetes operations 191 * 192 * @see #setTimeout(long) 193 */ 194 public long getTimeout() { 195 return this.timeout; 196 } 197 198 /** 199 * Sets the timeout value, in seconds, for Kubernetes operations. 200 * 201 * @param timeout the timeout value, in seconds, for Kubernetes 202 * operations 203 * 204 * @see #getTimeout() 205 */ 206 public void setTimeout(final long timeout) { 207 this.timeout = timeout; 208 } 209 210 /** 211 * Adds a {@link ReleaseTestListener} that will be {@linkplain 212 * ReleaseTestListener#releaseTested(ReleaseTestEvent) notified when 213 * the tests complete}. 214 * 215 * @param listener the {@link ReleaseTestListener} to add; may be 216 * {@code null} in which case no action will be taken 217 * 218 * @see #removeReleaseTestListener(ReleaseTestListener) 219 * 220 * @see #getReleaseTestListenersList() 221 */ 222 public void addReleaseTestListener(final ReleaseTestListener listener) { 223 if (listener != null) { 224 if (this.releaseTestListeners == null) { 225 this.releaseTestListeners = new ArrayList<>(); 226 } 227 this.releaseTestListeners.add(listener); 228 } 229 } 230 231 /** 232 * Removes a {@link ReleaseTestListener} from this {@link 233 * TestReleaseMojo}. 234 * 235 * @param listener the {@link ReleaseTestListener} to remove; may be 236 * {@code null} in which case no action will be taken 237 * 238 * @see #addReleaseTestListener(ReleaseTestListener) 239 * 240 * @see #getReleaseTestListenersList() 241 */ 242 public void removeReleaseTestListener(final ReleaseTestListener listener) { 243 if (listener != null && this.releaseTestListeners != null) { 244 this.releaseTestListeners.remove(listener); 245 } 246 } 247 248 /** 249 * Invokes the {@link #getReleaseTestListenersList()} method and 250 * {@linkplain Collection#toArray(Object[]) converts its return 251 * value to an array}. 252 * 253 * <p>This method never returns {@code null}.</p> 254 * 255 * <p>Overrides of this method must not return {@code null}.</p> 256 * 257 * @return a non-{@code null} array of {@link ReleaseTestListener}s 258 * 259 * @see #getReleaseTestListenersList() 260 */ 261 public ReleaseTestListener[] getReleaseTestListeners() { 262 final Collection<ReleaseTestListener> listeners = this.getReleaseTestListenersList(); 263 if (listeners == null || listeners.isEmpty()) { 264 return new ReleaseTestListener[0]; 265 } else { 266 return listeners.toArray(new ReleaseTestListener[listeners.size()]); 267 } 268 } 269 270 /** 271 * Returns the {@link List} of {@link ReleaseTestListener}s whose 272 * elements will be {@linkplain 273 * ReleaseTestListener#releaseTested(ReleaseTestEvent) notified when 274 * the tests complete}. 275 * 276 * <p>This method may return {@code null}.</p> 277 * 278 * <p>Overrides of this method are permitted to return {@code null}.</p> 279 * 280 * @return a {@link List} of {@link ReleaseTestListener}s, or {@code null} 281 * 282 * @see #setReleaseTestListenersList(List) 283 * 284 * @see #addReleaseTestListener(ReleaseTestListener) 285 * 286 * @see #removeReleaseTestListener(ReleaseTestListener) 287 */ 288 public List<ReleaseTestListener> getReleaseTestListenersList() { 289 return this.releaseTestListeners; 290 } 291 292 /** 293 * Installs the {@link List} of {@link ReleaseTestListener}s whose 294 * elements will be {@linkplain 295 * ReleaseTestListener#releaseTested(ReleaseTestEvent) notified when 296 * the tests complete}. 297 * 298 * @param releaseTestListeners the {@link List} of {@link ReleaseTestListener}s whose 299 * elements will be {@linkplain 300 * ReleaseTestListener#releaseTested(ReleaseTestEvent) notified when 301 * the tests complete}; may be {@code null} 302 * 303 * @see #getReleaseTestListenersList() 304 * 305 * @see #addReleaseTestListener(ReleaseTestListener) 306 * 307 * @see #removeReleaseTestListener(ReleaseTestListener) 308 */ 309 public void setReleaseTestListenersList(final List<ReleaseTestListener> releaseTestListeners) { 310 this.releaseTestListeners = releaseTestListeners; 311 } 312 313}