001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2017-2018 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.chart.repository;
018
019import java.io.BufferedInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022
023import java.net.URI;
024import java.net.URISyntaxException;
025
026import java.nio.file.Files;
027import java.nio.file.Path;
028import java.nio.file.Paths;
029
030import java.time.Instant;
031
032import java.util.Collection;
033import java.util.Collections;
034import java.util.Date;
035import java.util.LinkedHashSet;
036import java.util.Map;
037import java.util.Objects;
038import java.util.Set;
039
040import java.util.regex.Pattern;
041
042import hapi.chart.ChartOuterClass.Chart;
043
044import org.microbean.development.annotation.Experimental;
045
046import org.yaml.snakeyaml.Yaml;
047
048import org.yaml.snakeyaml.constructor.SafeConstructor;
049
050import org.microbean.helm.chart.resolver.AbstractChartResolver;
051import org.microbean.helm.chart.resolver.ChartResolverException;
052
053/**
054 * A repository of {@link ChartRepository} instances, normally built
055 * from a Helm {@code repositories.yaml} file.
056 *
057 * @author <a href="https://about.me/lairdnelson"
058 * target="_parent">Laird Nelson</a>
059 */
060@Experimental
061public class ChartRepositoryRepository extends AbstractChartResolver {
062
063
064  /*
065   * Static fields.
066   */
067
068
069  /**
070   * A {@link Pattern} that matches a single solidus ("{@code /}").
071   *
072   * <p>This field is never {@code null}.</p>
073   */
074  private static final Pattern slashPattern = Pattern.compile("/");
075
076  /**
077   * A {@link HelmHome} instance representing the directory tree where
078   * Helm stores its local information.
079   *
080   * <p>This field is never {@code null}.</p>
081   */
082  private static final HelmHome helmHome = new HelmHome();
083
084
085  /*
086   * Instance fields.
087   */
088
089
090  /**
091   * The ({@linkplain Collections#unmodifiableSet(Set) immutable})
092   * {@link Set} of {@link ChartRepository} instances managed by this
093   * {@link ChartRepositoryRepository}.
094   *
095   * <p>This field is never {@code null}.</p>
096   *
097   * @see #ChartRepositoryRepository(Set)
098   *
099   * @see #getChartRepositories()
100   */
101  private final Set<ChartRepository> chartRepositories;
102
103
104  /*
105   * Constructors.
106   */
107
108
109  /**
110   * Creates a new {@link ChartRepositoryRepository}.
111   *
112   * @param chartRepositories the {@link Set} of {@link
113   * ChartRepository} instances to be managed by this {@link
114   * ChartRepositoryRepository}; may be {@code null}; copied by value
115   *
116   * @see #getChartRepositories()
117   */
118  public ChartRepositoryRepository(final Set<? extends ChartRepository> chartRepositories) {
119    super();
120    if (chartRepositories == null || chartRepositories.isEmpty()) {
121      this.chartRepositories = Collections.emptySet();
122    } else {
123      this.chartRepositories = Collections.unmodifiableSet(new LinkedHashSet<>(chartRepositories));
124    }
125  }
126
127
128  /*
129   * Instance methods.
130   */
131
132
133  /**
134   * Returns the non-{@code null} {@linkplain
135   * Collections#unmodifiableSet(Set) immutable} {@link Set} of {@link
136   * ChartRepository} instances managed by this {@link
137   * ChartRepositoryRepository}.
138   *
139   * @return the non-{@code null} {@linkplain
140   * Collections#unmodifiableSet(Set) immutable} {@link Set} of {@link
141   * ChartRepository} instances managed by this {@link
142   * ChartRepositoryRepository}
143   */
144  public final Set<ChartRepository> getChartRepositories() {
145    return this.chartRepositories;
146  }
147
148  /**
149   * Returns the {@link ChartRepository} managed by this {@link
150   * ChartRepositoryRepository} with the supplied {@code name}, or
151   * {@code null} if there is no such {@link ChartRepository}.
152   *
153   * @param name the {@linkplain ChartRepository#getName() name} of
154   * the {@link ChartRepository} to return; must not be {@code null}
155   *
156   * @return the {@link ChartRepository} managed by this {@link
157   * ChartRepositoryRepository} with the supplied {@code name}, or
158   * {@code null}
159   *
160   * @exception NullPointerException if {@code name} is {@code null}
161   */
162  public ChartRepository getChartRepository(final String name) {
163    Objects.requireNonNull(name);
164    ChartRepository returnValue = null;
165    final Collection<? extends ChartRepository> repos = this.getChartRepositories();
166    if (repos != null && !repos.isEmpty()) {
167      for (final ChartRepository repo : repos) {
168        if (repo != null && name.equals(repo.getName())) {
169          returnValue = repo;
170        }
171      }
172    }
173    return returnValue;
174  }
175
176  /**
177   * {@inheritDoc}
178   *
179   * <p>This implementation splits the supplied slash-delimited {@code
180   * chartName} into a <em>chart repository name</em> and a <em>chart
181   * name</em>, uses the chart repository name to {@linkplain
182   * #getChartRepository(String) locate a suitable
183   * <code>ChartRepository</code>}, and then calls {@link
184   * ChartRepository#resolve(String, String)} with the chart name and
185   * the supplied {@code chartVersion}, and returns the result.</p>
186   *
187   * @param chartName a slash-separated {@link String} whose first
188   * component is a {@linkplain ChartRepository#getName() chart
189   * repository name} and whose second component is a Helm chart name;
190   * must not be {@code null}
191   *
192   * @param chartVersion the version of the chart to resolve; may be
193   * {@code null} in which case "latest" semantics are implied
194   *
195   * @return a {@link Chart.Builder}, or {@code null}
196   *
197   * @see #resolve(String, String, String)
198   */
199  @Override
200  public Chart.Builder resolve(final String chartName, final String chartVersion) throws ChartResolverException {
201    Objects.requireNonNull(chartName);
202    Chart.Builder returnValue = null;
203    final String[] parts = slashPattern.split(chartName, 2);
204    if (parts != null && parts.length == 2) {
205      returnValue = this.resolve(parts[0], parts[1], chartVersion);
206    }
207    return returnValue;
208  }
209
210  /**
211   * Uses the supplied {@code repositoryName}, {@code chartName} and
212   * {@code chartVersion} parameters to find an appropriate Helm chart
213   * and returns it in the form of a {@link Chart.Builder} object.
214   *
215   * <p>This implementation uses the supplied {@code repositoryName}
216   * to {@linkplain #getChartRepository(String) locate a suitable
217   * <code>ChartRepository</code>}, and then calls {@link
218   * ChartRepository#resolve(String, String)} with the chart name and
219   * the supplied {@code chartVersion}, and returns the result.</p>
220   *
221   * @param repositoryName a {@linkplain ChartRepository#getName()
222   * chart repository name}; must not be {@code null}
223   *
224   * @param chartName a Helm chart name; must not be {@code null}
225   *
226   * @param chartVersion the version of the Helm chart to select; may
227   * be {@code null} in which case "latest" semantics are implied
228   *
229   * @return a {@link Chart.Builder}, or {@code null}
230   *
231   * @exception ChartResolverException if there was a problem with
232   * resolution
233   *
234   * @exception NullPointerException if {@code repositoryName} or
235   * {@code chartName} is {@code null}
236   *
237   * @see #getChartRepository(String)
238   *
239   * @see ChartRepository#getName()
240   *
241   * @see ChartRepository#resolve(String, String)
242   */
243  public Chart.Builder resolve(final String repositoryName, final String chartName, final String chartVersion) throws ChartResolverException {
244    Objects.requireNonNull(repositoryName);
245    Objects.requireNonNull(chartName);
246    Chart.Builder returnValue = null;
247    final ChartRepository repo = this.getChartRepository(repositoryName);
248    if (repo != null) {
249      final Chart.Builder candidate = repo.resolve(chartName, chartVersion);
250      if (candidate != null) {
251        returnValue = candidate;
252      }
253    }
254    return returnValue;
255  }
256
257
258  /*
259   * Static methods.
260   */
261
262  
263  /**
264   * Creates and returns a new {@link ChartRepositoryRepository} from
265   * the contents of a {@code repositories.yaml} file typically
266   * located in the {@code ~/.helm/repository} directory.
267   *
268   * <p>This method never returns {@code null}.</p>
269   *
270   * @return a new {@link ChartRepositoryRepository}; never {@code
271   * null}
272   *
273   * @exception IOException if there was a problem reading the file
274   *
275   * @exception URISyntaxException if there was an invalid URI in the
276   * file
277   *
278   * @see #fromHelmRepositoriesYaml(boolean)
279   */
280  public static final ChartRepositoryRepository fromHelmRepositoriesYaml() throws IOException, URISyntaxException {
281    return fromHelmRepositoriesYaml(false);
282  }
283
284  /**
285   * Creates and returns a new {@link ChartRepositoryRepository} from
286   * the contents of a {@code repositories.yaml} file typically
287   * located in the {@code ~/.helm/repository} directory.
288   *
289   * <p>This method never returns {@code null}.</p>
290   *
291   * @param reifyHelmHomeIfNecessary if {@code true} and, for whatever
292   * reason, the local Helm home directory structure needs to be
293   * partially or entirely created, then this method will attempt to
294   * reify it
295   *
296   * @return a new {@link ChartRepositoryRepository}; never {@code
297   * null}
298   *
299   * @exception IOException if there was a problem reading the file or
300   * reifying the local Helm home directory structure
301   *
302   * @exception URISyntaxException if there was an invalid URI in the
303   * file
304   *
305   * @see #fromYaml(InputStream, Path, Path, boolean, ChartRepositoryFactory)
306   */
307  public static final ChartRepositoryRepository fromHelmRepositoriesYaml(final boolean reifyHelmHomeIfNecessary) throws IOException, URISyntaxException {
308    if (reifyHelmHomeIfNecessary) {
309      helmHome.reify();
310    }
311    try (final InputStream stream = new BufferedInputStream(Files.newInputStream(helmHome.toPath().resolve("repository/repositories.yaml")))) {
312      return fromYaml(stream, null, null, false /* already reified */, null);
313    }
314  }
315
316  /**
317   * Creates and returns a new {@link ChartRepositoryRepository} from
318   * the contents of a {@code repositories.yaml} file represented by
319   * the supplied {@link InputStream}.
320   *
321   * @param stream the {@link InputStream} to read from; must not be
322   * {@code null}
323   *
324   * @return a new {@link ChartRepositoryRepository}; never {@code
325   * null}
326   *
327   * @exception IOException if there was a problem reading the file
328   *
329   * @exception URISyntaxException if there was an invalid URI in the
330   * file
331   *
332   * @see #fromYaml(InputStream, Path, Path, boolean, ChartRepositoryFactory)
333   */
334  public static final ChartRepositoryRepository fromYaml(final InputStream stream) throws IOException, URISyntaxException {
335    return fromYaml(stream, null, null, false, null);
336  }
337
338  /**
339   * Creates and returns a new {@link ChartRepositoryRepository} from
340   * the contents of a {@code repositories.yaml} file represented by
341   * the supplied {@link InputStream}.
342   *
343   * @param stream the {@link InputStream} to read from; must not be
344   * {@code null}
345   *
346   * @param reifyHelmHomeIfNecessary if {@code true} and, for whatever
347   * reason, the local Helm home directory structure needs to be
348   * partially or entirely created, then this method will attempt to
349   * reify it
350   *
351   * @return a new {@link ChartRepositoryRepository}; never {@code
352   * null}
353   *
354   * @exception IOException if there was a problem reading the file or
355   * reifying the local Helm home directory structure
356   *
357   * @exception URISyntaxException if there was an invalid URI in the
358   * file
359   *
360   * @see #fromYaml(InputStream, Path, Path, boolean, ChartRepositoryFactory)
361   */
362  public static final ChartRepositoryRepository fromYaml(final InputStream stream, final boolean reifyHelmHomeIfNecessary) throws IOException, URISyntaxException {
363    return fromYaml(stream, null, null, reifyHelmHomeIfNecessary, null);
364  }
365
366  /**
367   * Creates and returns a new {@link ChartRepositoryRepository} from
368   * the contents of a {@code repositories.yaml} file represented by
369   * the supplied {@link InputStream}.
370   *
371   * @param stream the {@link InputStream} to read from; must not be
372   * {@code null}
373   *
374   * @param archiveCacheDirectory an {@linkplain Path#isAbsolute()
375   * absolute} {@link Path} representing a directory where Helm chart
376   * archives may be stored; if {@code null} then a {@link Path}
377   * beginning with the absolute directory represented by the value of
378   * the {@code helm.home} system property, or the value of the {@code
379   * HELM_HOME} environment variable, appended with {@code
380   * cache/archive} will be used instead
381   *
382   * @param indexCacheDirectory an {@linkplain Path#isAbsolute()
383   * absolute} {@link Path} representing a directory that the supplied
384   * {@code cachedIndexPath} parameter value will be considered to be
385   * relative to; will be ignored and hence may be {@code null} if the
386   * supplied {@code cachedIndexPath} parameter value {@linkplain
387   * Path#isAbsolute()}
388   *
389   * @return a new {@link ChartRepositoryRepository}; never {@code
390   * null}
391   *
392   * @exception IOException if there was a problem reading the file
393   *
394   * @exception URISyntaxException if there was an invalid URI in the
395   * file
396   *
397   * @see #fromYaml(InputStream, Path, Path, boolean, ChartRepositoryFactory)
398   */
399  public static final ChartRepositoryRepository fromYaml(final InputStream stream, Path archiveCacheDirectory, Path indexCacheDirectory) throws IOException, URISyntaxException {
400    return fromYaml(stream, archiveCacheDirectory, indexCacheDirectory, false, null);
401  }
402
403  /**
404   * Creates and returns a new {@link ChartRepositoryRepository} from
405   * the contents of a {@code repositories.yaml} file represented by
406   * the supplied {@link InputStream}.
407   *
408   * @param stream the {@link InputStream} to read from; must not be
409   * {@code null}
410   *
411   * @param archiveCacheDirectory an {@linkplain Path#isAbsolute()
412   * absolute} {@link Path} representing a directory where Helm chart
413   * archives may be stored; if {@code null} then a {@link Path}
414   * beginning with the absolute directory represented by the value of
415   * the {@code helm.home} system property, or the value of the {@code
416   * HELM_HOME} environment variable, appended with {@code
417   * cache/archive} will be used instead
418   *
419   * @param indexCacheDirectory an {@linkplain Path#isAbsolute()
420   * absolute} {@link Path} representing a directory that the supplied
421   * {@code cachedIndexPath} parameter value will be considered to be
422   * relative to; will be ignored and hence may be {@code null} if the
423   * supplied {@code cachedIndexPath} parameter value {@linkplain
424   * Path#isAbsolute()}
425   *
426   * @param reifyHelmHomeIfNecessary if {@code true} and, for whatever
427   * reason, the local Helm home directory structure needs to be
428   * partially or entirely created, then this method will attempt to
429   * reify it
430   *
431   * @return a new {@link ChartRepositoryRepository}; never {@code
432   * null}
433   *
434   * @exception IOException if there was a problem reading the file or
435   * reifying the local Helm home directory structure
436   *
437   * @exception URISyntaxException if there was an invalid URI in the
438   * file
439   *
440   * @see #fromYaml(InputStream, Path, Path, boolean, ChartRepositoryFactory)
441   */
442  public static final ChartRepositoryRepository fromYaml(final InputStream stream,
443                                                         Path archiveCacheDirectory,
444                                                         Path indexCacheDirectory,
445                                                         final boolean reifyHelmHomeIfNecessary)
446    throws IOException, URISyntaxException {
447    return fromYaml(stream, archiveCacheDirectory, indexCacheDirectory, reifyHelmHomeIfNecessary, null);
448  }
449
450  /**
451   * Creates and returns a new {@link ChartRepositoryRepository} from
452   * the contents of a {@code repositories.yaml} file represented by
453   * the supplied {@link InputStream}.
454   *
455   * @param stream the {@link InputStream} to read from; must not be
456   * {@code null}
457   *
458   * @param archiveCacheDirectory an {@linkplain Path#isAbsolute()
459   * absolute} {@link Path} representing a directory where Helm chart
460   * archives may be stored; if {@code null} then a {@link Path}
461   * beginning with the absolute directory represented by the value of
462   * the {@code helm.home} system property, or the value of the {@code
463   * HELM_HOME} environment variable, appended with {@code
464   * cache/archive} will be used instead
465   *
466   * @param indexCacheDirectory an {@linkplain Path#isAbsolute()
467   * absolute} {@link Path} representing a directory that the supplied
468   * {@code cachedIndexPath} parameter value will be considered to be
469   * relative to; will be ignored and hence may be {@code null} if the
470   * supplied {@code cachedIndexPath} parameter value {@linkplain
471   * Path#isAbsolute()}
472   *
473   * @param factory a {@link ChartRepositoryFactory} that can create
474   * {@link ChartRepository} instances; may be {@code null} in which
475   * case the {@link ChartRepository#ChartRepository(String, URI,
476   * Path, Path, Path)} constructor will be used instead
477   *
478   * @return a new {@link ChartRepositoryRepository}; never {@code
479   * null}
480   *
481   * @exception IOException if there was a problem reading the file
482   *
483   * @exception URISyntaxException if there was an invalid URI in the
484   * file
485   *
486   * @see #fromYaml(InputStream, Path, Path, boolean, ChartRepositoryFactory)
487   */
488  public static final ChartRepositoryRepository fromYaml(final InputStream stream,
489                                                         Path archiveCacheDirectory,
490                                                         Path indexCacheDirectory,
491                                                         ChartRepositoryFactory factory)
492    throws IOException, URISyntaxException {
493    return fromYaml(stream, archiveCacheDirectory, indexCacheDirectory, false, factory);
494  }
495
496  /**
497   * Creates and returns a new {@link ChartRepositoryRepository} from
498   * the contents of a {@code repositories.yaml} file represented by
499   * the supplied {@link InputStream}.
500   *
501   * @param stream the {@link InputStream} to read from; must not be
502   * {@code null}
503   *
504   * @param archiveCacheDirectory an {@linkplain Path#isAbsolute()
505   * absolute} {@link Path} representing a directory where Helm chart
506   * archives may be stored; if {@code null} then a {@link Path}
507   * beginning with the absolute directory represented by the value of
508   * the {@code helm.home} system property, or the value of the {@code
509   * HELM_HOME} environment variable, appended with {@code
510   * cache/archive} will be used instead
511   *
512   * @param indexCacheDirectory an {@linkplain Path#isAbsolute()
513   * absolute} {@link Path} representing a directory that the supplied
514   * {@code cachedIndexPath} parameter value will be considered to be
515   * relative to; will be ignored and hence may be {@code null} if the
516   * supplied {@code cachedIndexPath} parameter value {@linkplain
517   * Path#isAbsolute()}
518   *
519   * @param reifyHelmHomeIfNecessary if {@code true} and, for whatever
520   * reason, the local Helm home directory structure needs to be
521   * partially or entirely created, then this method will attempt to
522   * reify it
523   *
524   * @param factory a {@link ChartRepositoryFactory} that can create
525   * {@link ChartRepository} instances; may be {@code null} in which
526   * case the {@link ChartRepository#ChartRepository(String, URI,
527   * Path, Path, Path)} constructor will be used instead
528   *
529   * @return a new {@link ChartRepositoryRepository}; never {@code
530   * null}
531   *
532   * @exception IOException if there was a problem reading the file or
533   * reifying the local Helm home directory structure
534   *
535   * @exception URISyntaxException if there was an invalid URI in the
536   * file
537   */
538  public static final ChartRepositoryRepository fromYaml(final InputStream stream,
539                                                         Path archiveCacheDirectory,
540                                                         Path indexCacheDirectory,
541                                                         final boolean reifyHelmHomeIfNecessary,
542                                                         ChartRepositoryFactory factory)
543    throws IOException, URISyntaxException {
544    Objects.requireNonNull(stream);
545    if (factory == null) {
546      factory = ChartRepository::new;
547    }
548    boolean reified = false;
549    Path helmHomePath = null;
550    if (archiveCacheDirectory == null) {
551      helmHomePath = helmHome.toPath();
552      assert helmHomePath != null;
553      archiveCacheDirectory = helmHomePath.resolve("cache/archive");
554      assert archiveCacheDirectory != null;
555      if (reifyHelmHomeIfNecessary) {
556        helmHome.reify();
557        reified = true;
558      }
559    }
560    if (!Files.isDirectory(archiveCacheDirectory)) {
561      throw new IllegalArgumentException("!Files.isDirectory(archiveCacheDirectory): " + archiveCacheDirectory);
562    }
563    if (indexCacheDirectory == null) {
564      if (helmHomePath == null) {
565        helmHomePath = helmHome.toPath();
566        assert helmHomePath != null;
567      }
568      indexCacheDirectory = helmHomePath.resolve("repository/cache");
569      assert indexCacheDirectory != null;
570      if (!reified && reifyHelmHomeIfNecessary) {
571        helmHome.reify();
572        reified = true;
573      }
574    }
575    if (!Files.isDirectory(indexCacheDirectory)) {
576      throw new IllegalArgumentException("!Files.isDirectory(indexCacheDirectory): " + indexCacheDirectory);
577    }
578    final Map<?, ?> map = new Yaml(new SafeConstructor()).load(stream);
579    if (map == null || map.isEmpty()) {
580      throw new IllegalArgumentException("No data readable from stream: " + stream);
581    }
582    final Set<ChartRepository> chartRepositories;
583    @SuppressWarnings("unchecked")      
584    final Collection<? extends Map<?, ?>> repositories = (Collection<? extends Map<?, ?>>)map.get("repositories");
585    if (repositories == null || repositories.isEmpty()) {
586      chartRepositories = Collections.emptySet();
587    } else {
588      chartRepositories = new LinkedHashSet<>();
589      for (final Map<?, ?> repositoryMap : repositories) {
590        if (repositoryMap != null && !repositoryMap.isEmpty()) {
591          final String name = Objects.requireNonNull((String)repositoryMap.get("name"));
592          final URI uri = new URI((String)repositoryMap.get("url"));
593          Path cachedIndexPath = Objects.requireNonNull(Paths.get((String)repositoryMap.get("cache")));
594          if (!cachedIndexPath.isAbsolute()) {
595            cachedIndexPath = indexCacheDirectory.resolve(cachedIndexPath);
596            assert cachedIndexPath.isAbsolute();
597          }
598          
599          final ChartRepository chartRepository = factory.createChartRepository(name, uri, archiveCacheDirectory, indexCacheDirectory, cachedIndexPath);
600          if (chartRepository == null) {
601            throw new IllegalStateException("factory.createChartRepository() == null");
602          }
603          chartRepositories.add(chartRepository);
604        }      
605      }
606    }
607    return new ChartRepositoryRepository(chartRepositories);
608  }
609
610
611  /*
612   * Inner and nested classes.
613   */
614  
615
616  /**
617   * A factory for {@link ChartRepository} instances.
618   *
619   * @author <a href="https://about.me/lairdnelson"
620   * target="_parent">Laird Nelson</a>
621   *
622   * @see ChartRepository
623   */
624  @FunctionalInterface
625  public static interface ChartRepositoryFactory {
626
627    /**
628     * Creates a new {@link ChartRepository} and returns it.
629     *
630     * @param name the name of the chart repository; must not be
631     * {@code null}
632     *
633     * @param uri the {@link URI} to the root of the chart repository;
634     * must not be {@code null}
635     *
636     * @param archiveCacheDirectory an {@linkplain Path#isAbsolute()
637     * absolute} {@link Path} representing a directory where Helm chart
638     * archives may be stored; if {@code null} then often a {@link Path}
639     * beginning with the absolute directory represented by the value of
640     * the {@code helm.home} system property, or the value of the {@code
641     * HELM_HOME} environment variable, appended with {@code
642     * cache/archive} will be used instead
643     *
644     * @param indexCacheDirectory an {@linkplain Path#isAbsolute()
645     * absolute} {@link Path} representing a directory that the supplied
646     * {@code cachedIndexPath} parameter value will be considered to be
647     * relative to; <strong>will be ignored and hence may be {@code
648     * null}</strong> if the supplied {@code cachedIndexPath} parameter
649     * value {@linkplain Path#isAbsolute() is absolute}
650     *
651     * @param cachedIndexPath a {@link Path} naming the file that will
652     * store a copy of the chart repository's {@code index.yaml} file;
653     * if {@code null} then a {@link Path} relative to the absolute
654     * directory represented by the value of the {@code helm.home}
655     * system property, or the value of the {@code HELM_HOME}
656     * environment variable, and bearing a name consisting of the
657     * supplied {@code name} suffixed with {@code -index.yaml} will
658     * often be used instead
659     *
660     * @exception NullPointerException if either {@code name} or {@code
661     * uri} is {@code null}
662     *
663     * @exception IllegalArgumentException if {@code uri} is {@linkplain
664     * URI#isAbsolute() not absolute}, or if there is no existing "Helm
665     * home" directory, or if {@code archiveCacheDirectory} is
666     * non-{@code null} and either empty or not {@linkplain
667     * Path#isAbsolute()}
668     *
669     * @return a new, non-{@code null} {@link ChartRepository}
670     *
671     * @see ChartRepository#ChartRepository(String, URI, Path, Path,
672     * Path)
673     */
674    public ChartRepository createChartRepository(final String name, final URI uri, final Path archiveCacheDirectory, final Path indexCacheDirectory, final Path cachedIndexPath);
675    
676  }
677 
678}