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