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}