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.chart;
018
019import java.io.IOException;
020
021import java.util.Objects;
022
023import com.google.protobuf.AnyOrBuilder;
024import com.google.protobuf.ByteString;
025
026import hapi.chart.ChartOuterClass.ChartOrBuilder;
027import hapi.chart.ConfigOuterClass.ConfigOrBuilder;
028import hapi.chart.MetadataOuterClass.MetadataOrBuilder;
029import hapi.chart.TemplateOuterClass.TemplateOrBuilder;
030
031/**
032 * A partial {@link AbstractChartWriter} whose implementations save
033 * {@link ChartOrBuilder} objects to a destination that can
034 * be considered an archive of some sort.
035 *
036 * @author <a href="https://about.me/lairdnelson"
037 * target="_parent">Laird Nelson</a>
038 */
039public abstract class AbstractArchiveChartWriter extends AbstractChartWriter {
040
041
042  /*
043   * Constructors.
044   */
045
046
047  /**
048   * Creates a new {@link AbstractArchiveChartWriter}.
049   */
050  protected AbstractArchiveChartWriter() {
051    super();
052  }
053
054
055  /*
056   * Instance methods.
057   */
058
059
060  /**
061   * {@inheritDoc}
062   *
063   * <p>The {@link AbstractArchiveChartWriter} implementation stores a
064   * {@link String} representing the required path layout under a
065   * "{@code path}" key in the supplied {@link Context}.</p>
066   */
067  @Override
068  protected void beginWrite(final Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException {
069    Objects.requireNonNull(context);
070    Objects.requireNonNull(chartBuilder);
071    if (parent == chartBuilder) {
072      throw new IllegalArgumentException("parent == chartBuilder");
073    }
074    final MetadataOrBuilder metadata = chartBuilder.getMetadataOrBuilder();
075    if (metadata == null) {
076      throw new IllegalArgumentException("chartBuilder", new IllegalStateException("chartBuilder.getMetadata() == null"));
077    }
078    final String chartName = metadata.getName();
079    if (chartName == null) {
080      throw new IllegalArgumentException("chartBuilder", new IllegalStateException("chartBuilder.getMetadata().getName() == null"));
081    }
082    
083    if (parent == null) {
084      context.put("path", new StringBuilder(chartName).append("/").toString());
085    } else {
086      final MetadataOrBuilder parentMetadata = parent.getMetadataOrBuilder();
087      if (parentMetadata == null) {
088        throw new IllegalArgumentException("parent", new IllegalStateException("parent.getMetadata() == null"));
089      }
090      final String parentChartName = parentMetadata.getName();
091      if (parentChartName == null) {
092        throw new IllegalArgumentException("parent", new IllegalStateException("parent.getMetadata().getName() == null"));
093      }      
094      context.put("path", new StringBuilder(context.get("path", String.class)).append("charts/").append(chartName).append("/").toString());
095    }
096  }
097
098  /**
099   * {@inheritDoc}
100   *
101   * <p>The {@link AbstractArchiveChartWriter} implementation writes
102   * the {@linkplain #toYAML(Context, Object) YAML representation} of
103   * the supplied {@link MetadataOrBuilder} to an appropriate archive
104   * entry named {@code Chart.yaml} within the current chart path.</p>
105   *
106   * @exception NullPointerException if either {@code context} or
107   * {@code metadata} is {@code null}
108   */
109  @Override
110  protected void writeMetadata(final Context context, final MetadataOrBuilder metadata) throws IOException {
111    Objects.requireNonNull(context);
112    Objects.requireNonNull(metadata);
113
114    final String yaml = this.toYAML(context, metadata);
115    this.writeEntry(context, "Chart.yaml", yaml);
116  }
117
118  /**
119   * {@inheritDoc}
120   *
121   * <p>This implementation writes the {@linkplain #toYAML(Context,
122   * Object) YAML representation} of the supplied {@link
123   * ConfigOrBuilder} to an appropriate archive entry named {@code
124   * values.yaml} within the current chart path.</p>
125   *
126   * @exception NullPointerException if {@code context} is {@code
127   * null}
128   */
129  @Override
130  protected void writeConfig(final Context context, final ConfigOrBuilder config) throws IOException {
131    Objects.requireNonNull(context);
132    
133    if (config != null) {
134      final String raw = config.getRaw();
135      final String yaml;
136      if (raw == null || raw.isEmpty()) {
137        yaml = "";
138      } else {
139        yaml = this.toYAML(context, raw);
140      }
141      this.writeEntry(context, "values.yaml", yaml);
142    }
143  }
144
145  /**
146   * {@inheritDoc}
147   *
148   * <p>This implementation writes the {@linkplain
149   * TemplateOrBuilder#getData() data} of the supplied {@link
150   * TemplateOrBuilder} to an appropriate archive entry named in part
151   * by the return value of the {@link TemplateOrBuilder#getName()}
152   * method within the current chart path.</p>
153   *
154   * @exception NullPointerException if {@code context} is {@code
155   * null}
156   */
157  @Override
158  protected void writeTemplate(final Context context, final TemplateOrBuilder template) throws IOException {
159    Objects.requireNonNull(context);
160    
161    if (template != null) {
162      final String templateName = template.getName();
163      if (templateName != null && !templateName.isEmpty()) {
164        final ByteString data = template.getData();
165        if (data != null && data.size() > 0) {
166          final String dataString = data.toStringUtf8();
167          assert dataString != null;
168          assert !dataString.isEmpty();
169          this.writeEntry(context, templateName, dataString);
170        }
171      }
172    }
173  }
174
175  /**
176   * {@inheritDoc}
177   *
178   * <p>This implementation writes the {@linkplain
179   * AnyOrBuilder#getValue() contents} of the supplied {@link
180   * AnyOrBuilder} to an appropriate archive entry named in part by
181   * the return value of the {@link AnyOrBuilder#getTypeUrl()} method
182   * within the current chart path.</p>
183   *
184   * @exception NullPointerException if {@code context} is {@code
185   * null}
186   */
187  @Override
188  protected void writeFile(final Context context, final AnyOrBuilder file) throws IOException {
189    Objects.requireNonNull(context);
190    
191    if (file != null) {
192      final String fileName = file.getTypeUrl();
193      if (fileName != null && !fileName.isEmpty()) {
194        final ByteString data = file.getValue();
195        if (data != null && data.size() > 0) {
196          final String dataString = data.toStringUtf8();
197          assert dataString != null;
198          assert !dataString.isEmpty();
199          this.writeEntry(context, fileName, dataString);
200        }
201      }
202    }
203  }
204
205  /**
206   * {@inheritDoc}
207   *
208   * <p>This implementation ensures that the current chart path,
209   * residing under the "{@code path}" key in the supplied {@link
210   * AbstractChartWriter.Context}, is reset properly.</p>
211   *
212   * @exception NullPointerException if either {@code context} or
213   * {@code chartBuilder} is {@code null}
214   */
215  @Override
216  protected void endWrite(final Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException {
217    Objects.requireNonNull(context);
218    Objects.requireNonNull(chartBuilder);
219    if (chartBuilder == parent) {
220      throw new IllegalArgumentException("chartBuilder == parent");
221    }
222    
223    if (parent == null) {
224      context.remove("path");
225    } else {
226      final String path = context.get("path", String.class);
227      assert path != null;
228      final int chartsIndex = path.lastIndexOf("/charts/");
229      assert chartsIndex > 0;
230      context.put("path", path.substring(0, chartsIndex + 1));
231    }
232  }
233
234  /**
235   * Writes the supplied {@code contents} to an appropriate archive
236   * entry that is expected to be suffixed with the supplied {@code
237   * path} in the context of the write operation described by the
238   * supplied {@link Context}.
239   *
240   * @param context the {@link Context} describing the write operation
241   * in effect; must not be {@code null}
242   *
243   * @param path the path within an abstract archive to write;
244   * interpreted as being relative to the current notional chart path,
245   * whatever that might be; must not be {@code null} or {@linkplain
246   * String#isEmpty() empty}
247   *
248   * @param contents the contents to write; must not be {@code null}
249   *
250   * @exception IOException if a write error occurs
251   *
252   * @exception NullPointerException if {@code context}, {@code path}
253   * or {@code contents} is {@code null}
254   *
255   * @exception IllegalArgumentException if {@code path} {@linkplain
256   * String#isEmpty() is empty}
257   */
258  protected abstract void writeEntry(final Context context, final String path, final String contents) throws IOException;
259
260}