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}