001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017-208 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; 026import java.util.concurrent.Future; 027 028import hapi.release.ReleaseOuterClass.Release; 029import hapi.release.StatusOuterClass.Status; 030 031import hapi.services.tiller.Tiller.ListReleasesRequest; 032import hapi.services.tiller.Tiller.ListReleasesResponse; 033import hapi.services.tiller.Tiller.ListSort.SortBy; 034import hapi.services.tiller.Tiller.ListSort.SortOrder; 035 036import org.apache.maven.plugin.logging.Log; 037 038import org.apache.maven.plugins.annotations.Mojo; 039import org.apache.maven.plugins.annotations.Parameter; 040 041import org.microbean.helm.ReleaseManager; 042 043/** 044 * Retrieves releases matching certain criteria and notifies 045 * registered listeners. 046 * 047 * @author <a href="https://about.me/lairdnelson" 048 * target="_parent">Laird Nelson</a> 049 */ 050@Mojo(name = "list") 051public class ListReleasesMojo extends AbstractReleaseMojo { 052 053 054 /* 055 * Instance fields. 056 */ 057 058 059 /** 060 * The regular expression that will be applied to the list of 061 * releases. Only releases that match the filter will be considered 062 * for listing. 063 */ 064 @Parameter 065 private String filter; 066 067 /** 068 * The maximum number of releases to retrieve. 069 */ 070 @Parameter(defaultValue = "256") 071 private long limit; 072 073 /** 074 * The namespace from which releases will be listed. 075 */ 076 @Parameter 077 private String namespace; 078 079 /** 080 * The next release name in the list; used to offset from the start 081 * value. 082 */ 083 @Parameter 084 private String offset; 085 086 /** 087 * A <a 088 * href="https://microbean.github.io/microbean-helm/apidocs/hapi/services/tiller/Tiller.ListSort.SortBy.html">{@code 089 * SortBy}</a> indicating how the results should be sorted. Useful 090 * values are <a 091 * href="https://microbean.github.io/microbean-helm/apidocs/hapi/services/tiller/Tiller.ListSort.SortBy.html#NAME">{@code 092 * NAME}</a> and <a 093 * href="https://microbean.github.io/microbean-helm/apidocs/hapi/services/tiller/Tiller.ListSort.SortBy.html#LAST_RELEASED">{@code 094 * LAST_RELEASED}</a>. 095 */ 096 @Parameter(defaultValue = "NAME") 097 private SortBy sortBy; 098 099 /** 100 * A <a 101 * href="https://microbean.github.io/microbean-helm/apidocs/hapi/services/tiller/Tiller.ListSort.SortOrder.html">{@code 102 * SortOrder}</a> indicating whether results should appear sorted in 103 * ascending or descending order. 104 */ 105 @Parameter 106 private SortOrder sortOrder; 107 108 /** 109 * A {@link List} of <a 110 * href="https://microbean.github.io/microbean-helm/apidocs/hapi/release/StatusOuterClass.Status.Code.html">{@code 111 * StatusOuterClass.Status.Code}</a>s. Releases must have one of 112 * these status codes to be included in the list created by this 113 * goal. 114 */ 115 @Parameter 116 private List<Status.Code> statusCodes; 117 118 /** 119 * A {@link List} of <a 120 * href="apidocs/org/microbean/helm/maven/ReleaseDiscoveryListener.html">{@code 121 * ReleaseDiscoveryListener}</a>s whose elements will be notified of 122 * each release in the list created by this goal. 123 */ 124 @Parameter(alias = "releaseDiscoveryListenersList") 125 private List<ReleaseDiscoveryListener> releaseDiscoveryListeners; 126 127 128 /* 129 * Constructors. 130 */ 131 132 133 /** 134 * Creates a new {@link ListReleasesMojo}. 135 */ 136 public ListReleasesMojo() { 137 super(); 138 } 139 140 141 /* 142 * Protected instance methods. 143 */ 144 145 146 /** 147 * {@inheritDoc} 148 * 149 * <p>This implementation retrieves information about releases and 150 * {@linkplain 151 * ReleaseDiscoveryListener#releaseDiscovered(ReleaseDiscoveryEvent) 152 * notifies} {@linkplain #getReleaseDiscoveryListenersList() 153 * registered <code>ReleaseDiscoveryListener</code>s}.</p> 154 */ 155 @Override 156 protected void execute(final Callable<ReleaseManager> releaseManagerCallable) throws Exception { 157 Objects.requireNonNull(releaseManagerCallable); 158 final Log log = this.getLog(); 159 assert log != null; 160 161 final Collection<? extends ReleaseDiscoveryListener> listeners = this.getReleaseDiscoveryListenersList(); 162 if (listeners == null || listeners.isEmpty()) { 163 if (log.isInfoEnabled()) { 164 log.info("Skipping execution because there are no ReleaseDiscoveryListeners specified."); 165 } 166 return; 167 } 168 169 final ListReleasesRequest.Builder requestBuilder = ListReleasesRequest.newBuilder(); 170 assert requestBuilder != null; 171 172 final String filter = this.getFilter(); 173 if (filter != null) { 174 requestBuilder.setFilter(filter); 175 } 176 177 requestBuilder.setLimit(this.getLimit()); 178 179 String namespace = this.getNamespace(); 180 if (namespace == null || namespace.isEmpty()) { 181 final io.fabric8.kubernetes.client.Config configuration = this.getClientConfiguration(); 182 if (configuration == null) { 183 namespace = "default"; 184 } else { 185 namespace = configuration.getNamespace(); 186 if (namespace == null || namespace.isEmpty()) { 187 namespace = "default"; 188 } 189 } 190 } 191 this.validateNamespace(namespace); 192 requestBuilder.setNamespace(namespace); 193 194 final String offset = this.getOffset(); 195 if (offset != null) { 196 requestBuilder.setOffset(offset); 197 } 198 199 final SortBy sortBy = this.getSortBy(); 200 if (sortBy != null) { 201 requestBuilder.setSortBy(sortBy); 202 } 203 204 final SortOrder sortOrder = this.getSortOrder(); 205 if (sortOrder != null) { 206 requestBuilder.setSortOrder(sortOrder); 207 } 208 209 final Iterable<Status.Code> statusCodes = this.getStatusCodes(); 210 if (statusCodes != null) { 211 requestBuilder.addAllStatusCodes(statusCodes); 212 } 213 214 final ReleaseManager releaseManager = releaseManagerCallable.call(); 215 if (releaseManager == null) { 216 throw new IllegalStateException("releaseManagerCallable.call() == null"); 217 } 218 219 if (log.isInfoEnabled()) { 220 log.info("Listing releases in namespace " + namespace); 221 } 222 223 final Iterator<? extends ListReleasesResponse> listReleasesResponseIterator = releaseManager.list(requestBuilder.build()); 224 assert listReleasesResponseIterator != null; 225 while (listReleasesResponseIterator.hasNext()) { 226 final ListReleasesResponse response = listReleasesResponseIterator.next(); 227 assert response != null; 228 final ReleaseDiscoveryEvent event = new ReleaseDiscoveryEvent(this, response); 229 for (final ReleaseDiscoveryListener listener : listeners) { 230 if (listener != null) { 231 listener.releaseDiscovered(event); 232 } 233 } 234 } 235 236 } 237 238 239 /* 240 * Public instance methods. 241 */ 242 243 244 /** 245 * Returns the regular expression by which releases will be 246 * filtered. 247 * 248 * <p>This method may return {@code null}.</p> 249 * 250 * <p>Overrides of this method may return {@code null}.</p> 251 * 252 * @return the regular expression by which releases will be 253 * filtered, or {@code null} 254 * 255 * @see #setFilter(String) 256 */ 257 public String getFilter() { 258 return this.filter; 259 } 260 261 /** 262 * Sets the regular expression by which releases will be filtered. 263 * 264 * @param filter the regular expression by which releases will be 265 * filtered; may be {@code null} 266 * 267 * @see #getFilter() 268 */ 269 public void setFilter(final String filter) { 270 this.filter = filter; 271 } 272 273 /** 274 * Returns the maximum number of releases to retrieve. 275 * 276 * @return the maximum number of releases to retrieve 277 * 278 * @see #setLimit(long) 279 */ 280 public long getLimit() { 281 return this.limit; 282 } 283 284 /** 285 * Sets the maximum number of releases to retrieve. 286 * 287 * @param limit the maximum number of releases to retrieve 288 * 289 * @see #getLimit() 290 */ 291 public void setLimit(final long limit) { 292 this.limit = limit; 293 } 294 295 /** 296 * Returns the <a 297 * href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/">namespace</a> 298 * to which releases that are retrieved must belong. 299 * 300 * <p>This method may return {@code null}.</p> 301 * 302 * <p>Overrides of this method are permitted to return {@code 303 * null}.</p> 304 * 305 * @return the <a 306 * href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/">namespace</a> 307 * to which releases that are retrieved must belong, or {@code null} 308 * 309 * @see #setNamespace(String) 310 */ 311 public String getNamespace() { 312 return this.namespace; 313 } 314 315 /** 316 * Sets the <a 317 * href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/">namespace</a> 318 * to which releases that are retrieved must belong. 319 * 320 * @param namespace the <a 321 * href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/">namespace</a> 322 * to which releases that are retrieved must belong; may be {@code 323 * null} 324 * 325 * @see #getNamespace() 326 */ 327 public void setNamespace(final String namespace) { 328 this.validateNamespace(namespace); 329 this.namespace = namespace; 330 } 331 332 /** 333 * Returns the next release name from which listing should begin. 334 * 335 * <p>This method may return {@code null}.</p> 336 * 337 * <p>Overrides of this method are permitted to return {@code 338 * null}.</p> 339 * 340 * @return the next release name from which listing should begin, or 341 * {@code null} 342 * 343 * @see #setOffset(String) 344 */ 345 public String getOffset() { 346 return this.offset; 347 } 348 349 /** 350 * Sets the next release name from which listing should begin. 351 * 352 * @param offset the next release name from which listing should 353 * begin; may be {@code null} 354 * 355 * @see #getOffset() 356 */ 357 public void setOffset(final String offset) { 358 this.offset = offset; 359 } 360 361 /** 362 * Returns a {@link SortBy} indicating how the result list should be 363 * sorted. 364 * 365 * <p>This method may return {@code null}.</p> 366 * 367 * <p>Overrides of this method may return {@code null}.</p> 368 * 369 * @return a {@link SortBy} indicating how the result list should be 370 * sorted, or {@code null} 371 * 372 * @see #setSortBy(Tiller.ListSort.SortBy) 373 */ 374 public SortBy getSortBy() { 375 return this.sortBy; 376 } 377 378 /** 379 * Sets the {@link SortBy} indicating how the result list should be 380 * sorted. 381 * 382 * @param sortBy the {@link SortBy} indicating how the result list 383 * should be sorted; may be {@code null} 384 * 385 * @see #getSortBy() 386 */ 387 public void setSortBy(final SortBy sortBy) { 388 this.sortBy = sortBy; 389 } 390 391 /** 392 * Returns the {@link SortOrder} describing the order of the sorted 393 * result list. 394 * 395 * <p>This method may return {@code null}.</p> 396 * 397 * <p>Overrides of this method are permitted to return {@code null}.</p> 398 * 399 * @return the {@link SortOrder} describing the order of the sorted 400 * result list, or {@code null} 401 * 402 * @see #setSortOrder(Tiller.ListSort.SortOrder) 403 */ 404 public SortOrder getSortOrder() { 405 return this.sortOrder; 406 } 407 408 /** 409 * Sets the {@link SortOrder} describing the order of the sorted 410 * result list. 411 * 412 * @param sortOrder the {@link SortOrder} describing the order of 413 * the sorted result list; may be {@code null} 414 * 415 * @see #getSortOrder() 416 */ 417 public void setSortOrder(final SortOrder sortOrder) { 418 this.sortOrder = sortOrder; 419 } 420 421 /** 422 * Returns the {@link List} of {@link Status.Code} instances that 423 * describes the possible status codes a release must have in order 424 * to be considered for further listing. 425 * 426 * <p>This method may return {@code null}.</p> 427 * 428 * <p>Overrides of this method are permitted to return {@code null}.</p> 429 * 430 * @return a {@link List} of {@link Status.Code} instances that 431 * describes the possible status codes a release must have in order 432 * to be considered for further listing, or {@code null} 433 * 434 * @see #setStatusCodes(List) 435 */ 436 public List<Status.Code> getStatusCodes() { 437 return this.statusCodes; 438 } 439 440 /** 441 * Sets the {@link List} of {@link Status.Code} instances that 442 * describes the possible status codes a release must have in order 443 * to be considered for further listing. 444 * 445 * @param statusCodes a {@link List} of {@link Status.Code} 446 * instances that describes the possible status codes a release must 447 * have in order to be considered for further listing; may be {@code 448 * null} 449 * 450 * @see #getStatusCodes() 451 */ 452 public void setStatusCodes(final List<Status.Code> statusCodes) { 453 this.statusCodes = statusCodes; 454 } 455 456 /** 457 * Adds a {@link ReleaseDiscoveryListener} that will be {@linkplain 458 * ReleaseDiscoveryListener#releaseDiscovered(ReleaseDiscoveryEvent) 459 * notified} when a release is retrieved 460 * 461 * @param listener the {@link ReleaseDiscoveryListener} to add; may be 462 * {@code null} in which case no action will be taken 463 * 464 * @see #removeReleaseDiscoveryListener(ReleaseDiscoveryListener) 465 * 466 * @see #getReleaseDiscoveryListenersList() 467 */ 468 public void addReleaseDiscoveryListener(final ReleaseDiscoveryListener listener) { 469 if (listener != null) { 470 if (this.releaseDiscoveryListeners == null) { 471 this.releaseDiscoveryListeners = new ArrayList<>(); 472 } 473 this.releaseDiscoveryListeners.add(listener); 474 } 475 } 476 477 /** 478 * Removes a {@link ReleaseDiscoveryListener} from this {@link 479 * GetHistoryMojo}. 480 * 481 * @param listener the {@link ReleaseDiscoveryListener} to remove; may 482 * be {@code null} in which case no action will be taken 483 * 484 * @see #addReleaseDiscoveryListener(ReleaseDiscoveryListener) 485 * 486 * @see #getReleaseDiscoveryListenersList() 487 */ 488 public void removeReleaseDiscoveryListener(final ReleaseDiscoveryListener listener) { 489 if (listener != null && this.releaseDiscoveryListeners != null) { 490 this.releaseDiscoveryListeners.remove(listener); 491 } 492 } 493 494 /** 495 * Invokes the {@link #getReleaseDiscoveryListenersList()} method and 496 * {@linkplain Collection#toArray(Object[]) converts its return 497 * value to an array}. 498 * 499 * <p>This method never returns {@code null}.</p> 500 * 501 * <p>Overrides of this method must not return {@code null}.</p> 502 * 503 * @return a non-{@code null} array of {@link 504 * ReleaseDiscoveryListener}s 505 * 506 * @see #getReleaseDiscoveryListenersList() 507 */ 508 public ReleaseDiscoveryListener[] getReleaseDiscoveryListeners() { 509 final Collection<ReleaseDiscoveryListener> listeners = this.getReleaseDiscoveryListenersList(); 510 if (listeners == null || listeners.isEmpty()) { 511 return new ReleaseDiscoveryListener[0]; 512 } else { 513 return listeners.toArray(new ReleaseDiscoveryListener[listeners.size()]); 514 } 515 } 516 517 /** 518 * Returns the {@link List} of {@link ReleaseDiscoveryListener}s whose 519 * elements will be {@linkplain 520 * ReleaseDiscoveryListener#releaseDiscovered(ReleaseDiscoveryEvent) 521 * notified} when a release is retrieved. 522 * 523 * <p>This method may return {@code null}.</p> 524 * 525 * <p>Overrides of this method are permitted to return {@code 526 * null}.</p> 527 * 528 * @return a {@link List} of {@link ReleaseDiscoveryListener}s, or 529 * {@code null} 530 * 531 * @see #setReleaseDiscoveryListenersList(List) 532 * 533 * @see #addReleaseDiscoveryListener(ReleaseDiscoveryListener) 534 * 535 * @see #removeReleaseDiscoveryListener(ReleaseDiscoveryListener) 536 */ 537 public List<ReleaseDiscoveryListener> getReleaseDiscoveryListenersList() { 538 return this.releaseDiscoveryListeners; 539 } 540 541 /** 542 * Installs the {@link List} of {@link ReleaseDiscoveryListener}s 543 * whose elements will be {@linkplain 544 * ReleaseDiscoveryListener#releaseDiscovered(ReleaseDiscoveryEvent) 545 * notified when a release is retrieved}. 546 * 547 * @param releaseDiscoveryListeners the {@link List} of {@link 548 * ReleaseDiscoveryListener}s whose elements will be {@linkplain 549 * ReleaseDiscoveryListener#releaseDiscovered(ReleaseDiscoveryEvent) 550 * notified when a release is retrieved}; may be {@code 551 * null} 552 * 553 * @see #getReleaseDiscoveryListenersList() 554 * 555 * @see #addReleaseDiscoveryListener(ReleaseDiscoveryListener) 556 * 557 * @see #removeReleaseDiscoveryListener(ReleaseDiscoveryListener) 558 */ 559 public void setReleaseDiscoveryListenersList(final List<ReleaseDiscoveryListener> releaseDiscoveryListeners) { 560 this.releaseDiscoveryListeners = releaseDiscoveryListeners; 561 } 562 563}