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 = 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}