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 yaml = this.toYAML(context, config); 135 this.writeEntry(context, "values.yaml", yaml); 136 } 137 } 138 139 /** 140 * {@inheritDoc} 141 * 142 * <p>This implementation writes the {@linkplain 143 * TemplateOrBuilder#getData() data} of the supplied {@link 144 * TemplateOrBuilder} to an appropriate archive entry named in part 145 * by the return value of the {@link TemplateOrBuilder#getName()} 146 * method within the current chart path.</p> 147 * 148 * @exception NullPointerException if {@code context} is {@code 149 * null} 150 */ 151 @Override 152 protected void writeTemplate(final Context context, final TemplateOrBuilder template) throws IOException { 153 Objects.requireNonNull(context); 154 155 if (template != null) { 156 final String templateName = template.getName(); 157 if (templateName != null && !templateName.isEmpty()) { 158 final ByteString data = template.getData(); 159 if (data != null && data.size() > 0) { 160 final String dataString = data.toStringUtf8(); 161 assert dataString != null; 162 assert !dataString.isEmpty(); 163 this.writeEntry(context, templateName, dataString); 164 } 165 } 166 } 167 } 168 169 /** 170 * {@inheritDoc} 171 * 172 * <p>This implementation writes the {@linkplain 173 * AnyOrBuilder#getValue() contents} of the supplied {@link 174 * AnyOrBuilder} to an appropriate archive entry named in part by 175 * the return value of the {@link AnyOrBuilder#getTypeUrl()} method 176 * within the current chart path.</p> 177 * 178 * @exception NullPointerException if {@code context} is {@code 179 * null} 180 */ 181 @Override 182 protected void writeFile(final Context context, final AnyOrBuilder file) throws IOException { 183 Objects.requireNonNull(context); 184 185 if (file != null) { 186 final String fileName = file.getTypeUrl(); 187 if (fileName != null && !fileName.isEmpty()) { 188 final ByteString data = file.getValue(); 189 if (data != null && data.size() > 0) { 190 final String dataString = data.toStringUtf8(); 191 assert dataString != null; 192 assert !dataString.isEmpty(); 193 this.writeEntry(context, fileName, dataString); 194 } 195 } 196 } 197 } 198 199 /** 200 * {@inheritDoc} 201 * 202 * <p>This implementation ensures that the current chart path, 203 * residing under the "{@code path}" key in the supplied {@link 204 * AbstractChartWriter.Context}, is reset properly.</p> 205 * 206 * @exception NullPointerException if either {@code context} or 207 * {@code chartBuilder} is {@code null} 208 */ 209 @Override 210 protected void endWrite(final Context context, final ChartOrBuilder parent, final ChartOrBuilder chartBuilder) throws IOException { 211 Objects.requireNonNull(context); 212 Objects.requireNonNull(chartBuilder); 213 if (chartBuilder == parent) { 214 throw new IllegalArgumentException("chartBuilder == parent"); 215 } 216 217 if (parent == null) { 218 context.remove("path"); 219 } else { 220 final String path = context.get("path", String.class); 221 assert path != null; 222 final int chartsIndex = path.lastIndexOf("/charts/"); 223 assert chartsIndex > 0; 224 context.put("path", path.substring(0, chartsIndex + 1)); 225 } 226 } 227 228 /** 229 * Writes the supplied {@code contents} to an appropriate archive 230 * entry that is expected to be suffixed with the supplied {@code 231 * path} in the context of the write operation described by the 232 * supplied {@link Context}. 233 * 234 * @param context the {@link Context} describing the write operation 235 * in effect; must not be {@code null} 236 * 237 * @param path the path within an abstract archive to write; 238 * interpreted as being relative to the current notional chart path, 239 * whatever that might be; must not be {@code null} or {@linkplain 240 * String#isEmpty() empty} 241 * 242 * @param contents the contents to write; must not be {@code null} 243 * 244 * @exception IOException if a write error occurs 245 * 246 * @exception NullPointerException if {@code context}, {@code path} 247 * or {@code contents} is {@code null} 248 * 249 * @exception IllegalArgumentException if {@code path} {@linkplain 250 * String#isEmpty() is empty} 251 */ 252 protected abstract void writeEntry(final Context context, final String path, final String contents) throws IOException; 253 254}