github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/html.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net" 8 "os" 9 "path" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/Cloud-Foundations/Dominator/imagebuilder/logarchiver" 15 "github.com/Cloud-Foundations/Dominator/lib/filter" 16 "github.com/Cloud-Foundations/Dominator/lib/format" 17 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 18 "github.com/Cloud-Foundations/Dominator/lib/html" 19 libjson "github.com/Cloud-Foundations/Dominator/lib/json" 20 "github.com/Cloud-Foundations/Dominator/lib/stringutil" 21 ) 22 23 const codeStyle = `background-color: #eee; border: 1px solid #999; display: block; float: left;` 24 25 func streamNameText(streamName string, 26 autoRebuildStreams map[string]struct{}) string { 27 if _, ok := autoRebuildStreams[streamName]; ok { 28 return "<b>" + streamName + "</b>" 29 } else { 30 return streamName 31 } 32 } 33 34 func writeFilter(writer io.Writer, prefix string, filt *filter.Filter) { 35 if filt != nil && len(filt.FilterLines) > 0 { 36 fmt.Fprintln(writer, prefix, "Filter lines:<br>") 37 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 38 libjson.WriteWithIndent(writer, " ", filt.FilterLines) 39 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 40 } 41 } 42 43 func (stream *bootstrapStream) WriteHtml(writer io.Writer) { 44 fmt.Fprintf(writer, "Bootstrap command: <code>%s</code><br>\n", 45 strings.Join(stream.BootstrapCommand, " ")) 46 writeFilter(writer, "", stream.Filter) 47 packager := stream.builder.packagerTypes[stream.PackagerType] 48 packager.WriteHtml(writer) 49 writeFilter(writer, "Image ", stream.imageFilter) 50 if stream.imageTriggers != nil { 51 fmt.Fprintln(writer, "Image triggers:<br>") 52 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 53 libjson.WriteWithIndent(writer, " ", stream.imageTriggers.Triggers) 54 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 55 } 56 if len(stream.imageTags) > 0 { 57 fmt.Fprintln(writer, "Image tags:<br>") 58 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 59 libjson.WriteWithIndent(writer, " ", stream.imageTags) 60 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 61 } 62 } 63 64 func (b *Builder) getHtmlWriter(streamName string) html.HtmlWriter { 65 if stream := b.getBootstrapStream(streamName); stream != nil { 66 return stream 67 } 68 if stream := b.getNormalStream(streamName); stream != nil { 69 return stream 70 } 71 // Ensure a nil interface is returned, not a stream with value == nil. 72 return nil 73 } 74 75 func (b *Builder) showImageStream(writer io.Writer, streamName string) { 76 stream := b.getHtmlWriter(streamName) 77 if stream == nil { 78 fmt.Fprintf(writer, "<b>Stream: %s does not exist!</b>\n", streamName) 79 return 80 } 81 fmt.Fprintf(writer, "<h3>Information for stream: %s</h3>\n", streamName) 82 stream.WriteHtml(writer) 83 } 84 85 func (b *Builder) showImageStreams(writer io.Writer) { 86 streamNames := b.listAllStreamNames() 87 sort.Strings(streamNames) 88 fmt.Fprintln(writer, `<table border="1">`) 89 tw, _ := html.NewTableWriter(writer, true, 90 "Image Stream", "ManifestUrl", "ManifestDirectory") 91 autoRebuildStreams := stringutil.ConvertListToMap( 92 b.listStreamsToAutoRebuild(), false) 93 for _, streamName := range streamNames { 94 var manifestUrl, manifestDirectory string 95 if imageStream := b.getNormalStream(streamName); imageStream != nil { 96 manifestUrl = imageStream.ManifestUrl 97 manifestDirectory = imageStream.ManifestDirectory 98 } 99 tw.WriteRow("", "", 100 fmt.Sprintf("<a href=\"showImageStream?%s\">%s</a>", 101 streamName, streamNameText(streamName, autoRebuildStreams)), 102 manifestUrl, manifestDirectory) 103 } 104 tw.Close() 105 fmt.Fprintln(writer, "<br>") 106 } 107 108 func (b *Builder) writeHtml(writer io.Writer) { 109 fmt.Fprintf(writer, 110 "Number of image streams: <a href=\"showImageStreams\">%d</a><br>\n", 111 b.getNumStreams()) 112 fmt.Fprintln(writer, 113 "Image stream <a href=\"showDirectedGraph\">relationships</a><br>") 114 fmt.Fprintf(writer, 115 "Image server: <a href=\"http://%s/\">%s</a><p>\n", 116 b.imageServerAddress, b.imageServerAddress) 117 currentBuildNames := make([]string, 0) 118 currentBuildSlaves := make([]string, 0) 119 currentBuildTimes := make([]time.Time, 0) 120 goodBuilds := make(map[string]buildResultType) 121 failedBuilds := make(map[string]buildResultType) 122 b.buildResultsLock.RLock() 123 for name, info := range b.currentBuildInfos { 124 currentBuildNames = append(currentBuildNames, name) 125 currentBuildSlaves = append(currentBuildSlaves, info.slaveAddress) 126 currentBuildTimes = append(currentBuildTimes, info.startedAt) 127 } 128 for name, result := range b.lastBuildResults { 129 if result.error == nil { 130 goodBuilds[name] = result 131 } else { 132 failedBuilds[name] = result 133 } 134 } 135 b.buildResultsLock.RUnlock() 136 autoRebuildStreams := stringutil.ConvertListToMap( 137 b.listStreamsToAutoRebuild(), false) 138 currentTime := time.Now() 139 if len(currentBuildNames) > 0 { 140 fmt.Fprintln(writer, "Current image builds:<br>") 141 fmt.Fprintln(writer, `<table border="1">`) 142 columnNames := []string{"Image Stream", "Build log", "Duration"} 143 if b.slaveDriver != nil { 144 columnNames = append(columnNames, "Slave") 145 } 146 tw, _ := html.NewTableWriter(writer, true, columnNames...) 147 for index, streamName := range currentBuildNames { 148 columns := []string{ 149 streamNameText(streamName, autoRebuildStreams), 150 fmt.Sprintf("<a href=\"showCurrentBuildLog?%s#bottom\">log</a>", 151 streamName), 152 format.Duration(time.Since(currentBuildTimes[index])), 153 } 154 if b.slaveDriver != nil { 155 var slaveColumn string 156 if address := currentBuildSlaves[index]; address != "" { 157 host, _, err := net.SplitHostPort(address) 158 if err != nil { 159 host = address 160 } 161 slaveColumn = fmt.Sprintf("<a href=\"http://%s/\">%s</a>", 162 address, host) 163 } 164 columns = append(columns, slaveColumn) 165 } 166 tw.WriteRow("", "", columns...) 167 } 168 tw.Close() 169 fmt.Fprintln(writer, "<br>") 170 } 171 if len(failedBuilds) > 0 { 172 streamNames := make([]string, 0, len(failedBuilds)) 173 for streamName := range failedBuilds { 174 streamNames = append(streamNames, streamName) 175 } 176 sort.Strings(streamNames) 177 fmt.Fprintln(writer, "Failed image builds:<br>") 178 fmt.Fprintln(writer, `<table border="1">`) 179 tw, _ := html.NewTableWriter(writer, true, 180 "Image Stream", "Error", "Build log", "Duration", "Last attempt") 181 for _, streamName := range streamNames { 182 result := failedBuilds[streamName] 183 tw.WriteRow("", "", 184 streamNameText(streamName, autoRebuildStreams), 185 result.error.Error(), 186 fmt.Sprintf("<a href=\"showLastBuildLog?%s\">log</a>", 187 streamName), 188 format.Duration(result.finishTime.Sub(result.startTime)), 189 fmt.Sprintf("%s ago", 190 format.Duration(currentTime.Sub(result.finishTime))), 191 ) 192 } 193 tw.Close() 194 fmt.Fprintln(writer, "<br>") 195 } 196 if len(goodBuilds) > 0 { 197 streamNames := make([]string, 0, len(goodBuilds)) 198 for streamName := range goodBuilds { 199 streamNames = append(streamNames, streamName) 200 } 201 sort.Strings(streamNames) 202 fmt.Fprintln(writer, "Successful image builds:<br>") 203 fmt.Fprintln(writer, `<table border="1">`) 204 tw, _ := html.NewTableWriter(writer, true, "Image Stream", "Name", 205 "Build log", "Duration", "Age") 206 for _, streamName := range streamNames { 207 result := goodBuilds[streamName] 208 tw.WriteRow("", "", 209 streamNameText(streamName, autoRebuildStreams), 210 fmt.Sprintf("<a href=\"http://%s/showImage?%s\">%s</a>", 211 b.linksImageServerAddress, result.imageName, 212 result.imageName), 213 fmt.Sprintf("<a href=\"showLastBuildLog?%s\">log</a>", 214 streamName), 215 format.Duration(result.finishTime.Sub(result.startTime)), 216 fmt.Sprintf("%s ago", 217 format.Duration(currentTime.Sub(result.finishTime))), 218 ) 219 } 220 tw.Close() 221 fmt.Fprintln(writer, "<br>") 222 } 223 if _, ok := b.buildLogArchiver.(logarchiver.BuildLogReporter); ok { 224 fmt.Fprintln(writer, 225 "Build log <a href=\"showBuildLogArchive\">archive</a><br>") 226 } 227 } 228 229 func (stream *imageStreamType) WriteHtml(writer io.Writer) { 230 if len(stream.BuilderGroups) > 0 { 231 fmt.Fprintf(writer, "BuilderGroups: %s<br>\n", 232 strings.Join(stream.BuilderGroups, ", ")) 233 } 234 if len(stream.BuilderUsers) > 0 { 235 fmt.Fprintf(writer, "BuilderUsers: %s<br>\n", 236 strings.Join(stream.BuilderUsers, ", ")) 237 } 238 manifestLocation := stream.getManifestLocation(nil, nil) 239 fmt.Fprintf(writer, "Manifest URL: <code>%s</code><br>\n", 240 stream.ManifestUrl) 241 if manifestLocation.url != stream.ManifestUrl { 242 fmt.Fprintf(writer, "Manifest URL (expanded): <code>%s</code><br>\n", 243 manifestLocation.url) 244 } 245 fmt.Fprintf(writer, "Manifest Directory: <code>%s</code><br>\n", 246 stream.ManifestDirectory) 247 if manifestLocation.directory != stream.ManifestDirectory { 248 fmt.Fprintf(writer, 249 "Manifest Directory (expanded): <code>%s</code><br>\n", 250 manifestLocation.directory) 251 } 252 buildLog := new(bytes.Buffer) 253 manifestDirectory, sourceImageName, gitInfo, manifestBytes, _, err := 254 stream.getSourceImage(stream.builder, buildLog) 255 if err != nil { 256 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 257 return 258 } 259 defer os.RemoveAll(manifestDirectory) 260 if gitInfo != nil { 261 fmt.Fprintf(writer, 262 "Latest commit on branch: <code>%s</code>: <code>%s</code>s<br>\n", 263 gitInfo.branch, gitInfo.commitId) 264 } 265 if stream.builder.getHtmlWriter(sourceImageName) == nil { 266 fmt.Fprintf(writer, "SourceImage: <code>%s</code><br>\n", 267 sourceImageName) 268 } else { 269 fmt.Fprintf(writer, 270 "SourceImage: <a href=\"showImageStream?%s\"><code>%s</code></a><br>\n", 271 sourceImageName, sourceImageName) 272 } 273 if len(stream.Variables) > 0 { 274 fmt.Fprintln(writer, "Stream variables:<br>") 275 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 276 libjson.WriteWithIndent(writer, " ", stream.Variables) 277 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 278 } 279 fmt.Fprintln(writer, "Contents of <code>manifest</code> file:<br>") 280 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 281 writer.Write(manifestBytes) 282 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 283 packagesFile, err := os.Open(path.Join(manifestDirectory, "package-list")) 284 if err == nil { 285 defer packagesFile.Close() 286 fmt.Fprintln(writer, "Contents of <code>package-list</code> file:<br>") 287 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 288 io.Copy(writer, packagesFile) 289 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 290 } else if !os.IsNotExist(err) { 291 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 292 return 293 } 294 tagsFile, err := os.Open(path.Join(manifestDirectory, "tags.json")) 295 if err == nil { 296 defer tagsFile.Close() 297 fmt.Fprintln(writer, "Contents of <code>tags.json</code> file:<br>") 298 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 299 io.Copy(writer, tagsFile) 300 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 301 } else if !os.IsNotExist(err) { 302 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 303 return 304 } 305 if size, err := fsutil.GetTreeSize(manifestDirectory); err != nil { 306 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 307 return 308 } else { 309 fmt.Fprintf(writer, "Manifest tree size: %s<br>\n", 310 format.FormatBytes(size)) 311 } 312 fmt.Fprintln(writer, "<hr style=\"height:2px\"><font color=\"#bbb\">") 313 fmt.Fprintln(writer, "<b>Logging output:</b>") 314 fmt.Fprintln(writer, "<pre>") 315 io.Copy(writer, buildLog) 316 fmt.Fprintln(writer, "</pre>") 317 fmt.Fprintln(writer, "</font>") 318 } 319 320 func (packager *packagerType) WriteHtml(writer io.Writer) { 321 fmt.Fprintf(writer, "Clean command: <code>%s</code><br>\n", 322 strings.Join(packager.CleanCommand, " ")) 323 fmt.Fprintf(writer, "Install command: <code>%s</code><br>\n", 324 strings.Join(packager.InstallCommand, " ")) 325 fmt.Fprintf(writer, "List command: <code>%s</code><br>\n", 326 strings.Join(packager.ListCommand.ArgList, " ")) 327 if packager.ListCommand.SizeMultiplier > 1 { 328 fmt.Fprintf(writer, "List command size multiplier: %d<br>\n", 329 packager.ListCommand.SizeMultiplier) 330 } 331 fmt.Fprintf(writer, "Remove command: <code>%s</code><br>\n", 332 strings.Join(packager.RemoveCommand, " ")) 333 fmt.Fprintf(writer, "Update command: <code>%s</code><br>\n", 334 strings.Join(packager.UpdateCommand, " ")) 335 fmt.Fprintf(writer, "Upgrade command: <code>%s</code><br>\n", 336 strings.Join(packager.UpgradeCommand, " ")) 337 if len(packager.Verbatim) > 0 { 338 fmt.Fprintln(writer, "Verbatim lines:<br>") 339 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 340 libjson.WriteWithIndent(writer, " ", packager.Verbatim) 341 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 342 } 343 fmt.Fprintln(writer, "Package installer script:<br>") 344 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 345 packager.writePackageInstallerContents(writer) 346 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 347 }