github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imagebuilder/builder/html.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path" 11 "sort" 12 "strings" 13 "time" 14 15 "github.com/Cloud-Foundations/Dominator/lib/filter" 16 "github.com/Cloud-Foundations/Dominator/lib/format" 17 "github.com/Cloud-Foundations/Dominator/lib/html" 18 libjson "github.com/Cloud-Foundations/Dominator/lib/json" 19 ) 20 21 const codeStyle = `background-color: #eee; border: 1px solid #999; display: block; float: left;` 22 23 func writeFilter(writer io.Writer, prefix string, filt *filter.Filter) { 24 if filt != nil && len(filt.FilterLines) > 0 { 25 fmt.Fprintln(writer, prefix, "Filter lines:<br>") 26 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 27 libjson.WriteWithIndent(writer, " ", filt.FilterLines) 28 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 29 } 30 } 31 32 func (stream *bootstrapStream) WriteHtml(writer io.Writer) { 33 fmt.Fprintf(writer, "Bootstrap command: <code>%s</code><br>\n", 34 strings.Join(stream.BootstrapCommand, " ")) 35 writeFilter(writer, "", stream.Filter) 36 packager := stream.builder.packagerTypes[stream.PackagerType] 37 packager.WriteHtml(writer) 38 writeFilter(writer, "Image ", stream.imageFilter) 39 if stream.imageTriggers != nil { 40 fmt.Fprintln(writer, "Image triggers:<br>") 41 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 42 libjson.WriteWithIndent(writer, " ", stream.imageTriggers.Triggers) 43 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 44 } 45 } 46 47 func (b *Builder) getHtmlWriter(streamName string) html.HtmlWriter { 48 if stream := b.getBootstrapStream(streamName); stream != nil { 49 return stream 50 } 51 if stream := b.getNormalStream(streamName); stream != nil { 52 return stream 53 } 54 // Ensure a nil interface is returned, not a stream with value == nil. 55 return nil 56 } 57 58 func (b *Builder) showImageStream(writer io.Writer, streamName string) { 59 stream := b.getHtmlWriter(streamName) 60 if stream == nil { 61 fmt.Fprintf(writer, "<b>Stream: %s does not exist!</b>\n", streamName) 62 return 63 } 64 fmt.Fprintf(writer, "<h3>Information for stream: %s</h3>\n", streamName) 65 stream.WriteHtml(writer) 66 } 67 68 func (b *Builder) showImageStreams(writer io.Writer) { 69 streamNames := b.listAllStreamNames() 70 sort.Strings(streamNames) 71 fmt.Fprintln(writer, `<table border="1">`) 72 tw, _ := html.NewTableWriter(writer, true, 73 "Image Stream", "ManifestUrl", "ManifestDirectory") 74 for _, streamName := range streamNames { 75 var manifestUrl, manifestDirectory string 76 if imageStream := b.getNormalStream(streamName); imageStream != nil { 77 manifestUrl = imageStream.ManifestUrl 78 manifestDirectory = imageStream.ManifestDirectory 79 } 80 tw.WriteRow("", "", 81 fmt.Sprintf("<a href=\"showImageStream?%s\">%s</a>", 82 streamName, streamName), manifestUrl, manifestDirectory) 83 } 84 fmt.Fprintln(writer, "</table>") 85 } 86 87 func (b *Builder) writeHtml(writer io.Writer) { 88 fmt.Fprintf(writer, 89 "Number of image streams: <a href=\"showImageStreams\">%d</a><p>\n", 90 b.getNumNormalStreams()) 91 currentBuilds := make([]string, 0) 92 goodBuilds := make(map[string]buildResultType) 93 failedBuilds := make(map[string]buildResultType) 94 b.buildResultsLock.RLock() 95 for name := range b.currentBuildLogs { 96 currentBuilds = append(currentBuilds, name) 97 } 98 for name, result := range b.lastBuildResults { 99 if result.error == nil { 100 goodBuilds[name] = result 101 } else { 102 failedBuilds[name] = result 103 } 104 } 105 b.buildResultsLock.RUnlock() 106 currentTime := time.Now() 107 if len(currentBuilds) > 0 { 108 fmt.Fprintln(writer, "Current image builds:<br>") 109 fmt.Fprintln(writer, `<table border="1">`) 110 tw, _ := html.NewTableWriter(writer, true, "Image Stream", "Build log") 111 for _, streamName := range currentBuilds { 112 tw.WriteRow("", "", 113 streamName, 114 fmt.Sprintf("<a href=\"showCurrentBuildLog?%s#bottom\">log</a>", 115 streamName), 116 ) 117 } 118 fmt.Fprintln(writer, "</table><br>") 119 } 120 if len(failedBuilds) > 0 { 121 streamNames := make([]string, 0, len(failedBuilds)) 122 for streamName := range failedBuilds { 123 streamNames = append(streamNames, streamName) 124 } 125 sort.Strings(streamNames) 126 fmt.Fprintln(writer, "Failed image builds:<br>") 127 fmt.Fprintln(writer, `<table border="1">`) 128 tw, _ := html.NewTableWriter(writer, true, 129 "Image Stream", "Error", "Build log", "Last attempt") 130 for _, streamName := range streamNames { 131 result := failedBuilds[streamName] 132 tw.WriteRow("", "", 133 streamName, 134 result.error.Error(), 135 fmt.Sprintf("<a href=\"showLastBuildLog?%s\">log</a>", 136 streamName), 137 fmt.Sprintf("%s ago", 138 format.Duration(currentTime.Sub(result.finishTime))), 139 ) 140 } 141 fmt.Fprintln(writer, "</table><br>") 142 } 143 if len(goodBuilds) > 0 { 144 streamNames := make([]string, 0, len(goodBuilds)) 145 for streamName := range goodBuilds { 146 streamNames = append(streamNames, streamName) 147 } 148 sort.Strings(streamNames) 149 fmt.Fprintln(writer, "Successful image builds:<br>") 150 fmt.Fprintln(writer, `<table border="1">`) 151 tw, _ := html.NewTableWriter(writer, true, "Image Stream", "Name", 152 "Build log", "Duration", "Age") 153 for _, streamName := range streamNames { 154 result := goodBuilds[streamName] 155 tw.WriteRow("", "", 156 streamName, 157 fmt.Sprintf("<a href=\"http://%s/showImage?%s\">%s</a>", 158 b.imageServerAddress, result.imageName, result.imageName), 159 fmt.Sprintf("<a href=\"showLastBuildLog?%s\">log</a>", 160 streamName), 161 format.Duration(result.finishTime.Sub(result.startTime)), 162 fmt.Sprintf("%s ago", 163 format.Duration(currentTime.Sub(result.finishTime))), 164 ) 165 } 166 fmt.Fprintln(writer, "</table><br>") 167 } 168 } 169 170 func (stream *imageStreamType) WriteHtml(writer io.Writer) { 171 if len(stream.BuilderGroups) > 0 { 172 fmt.Fprintf(writer, "BuilderGroups: %s<br>\n", 173 strings.Join(stream.BuilderGroups, ", ")) 174 } 175 fmt.Fprintf(writer, "Manifest URL: <code>%s</code><br>\n", 176 stream.ManifestUrl) 177 fmt.Fprintf(writer, "Manifest Directory: <code>%s</code><br>\n", 178 stream.ManifestDirectory) 179 buildLog := new(bytes.Buffer) 180 manifestDirectory, gitInfo, err := stream.getManifest(stream.builder, 181 stream.name, "", nil, buildLog) 182 if err != nil { 183 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 184 return 185 } 186 defer os.RemoveAll(manifestDirectory) 187 if gitInfo != nil { 188 fmt.Fprintf(writer, 189 "Latest commit on branch: <code>%s</code>: <code>%s</code>s<br>\n", 190 gitInfo.branch, gitInfo.commitId) 191 } 192 manifestFilename := path.Join(manifestDirectory, "manifest") 193 manifestBytes, err := ioutil.ReadFile(manifestFilename) 194 if err != nil { 195 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 196 return 197 } 198 var manifest manifestConfigType 199 if err := json.Unmarshal(manifestBytes, &manifest); err != nil { 200 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 201 return 202 } 203 sourceImageName := os.Expand(manifest.SourceImage, 204 func(name string) string { 205 return stream.getenv()[name] 206 }) 207 if stream.builder.getHtmlWriter(sourceImageName) == nil { 208 fmt.Fprintf(writer, "SourceImage: <code>%s</code><br>\n", 209 sourceImageName) 210 } else { 211 fmt.Fprintf(writer, 212 "SourceImage: <a href=\"showImageStream?%s\"><code>%s</code></a><br>\n", 213 sourceImageName, sourceImageName) 214 } 215 fmt.Fprintln(writer, "Contents of <code>manifest</code> file:<br>") 216 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 217 writer.Write(manifestBytes) 218 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 219 packagesFile, err := os.Open(path.Join(manifestDirectory, "package-list")) 220 if err == nil { 221 defer packagesFile.Close() 222 fmt.Fprintln(writer, "Contents of <code>package-list</code> file:<br>") 223 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 224 io.Copy(writer, packagesFile) 225 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 226 } else if !os.IsNotExist(err) { 227 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 228 return 229 } 230 if size, err := getTreeSize(manifestDirectory); err != nil { 231 fmt.Fprintf(writer, "<b>%s</b><br>\n", err) 232 return 233 } else { 234 fmt.Fprintf(writer, "Manifest tree size: %s<br>\n", 235 format.FormatBytes(size)) 236 } 237 fmt.Fprintln(writer, "<hr style=\"height:2px\"><font color=\"#bbb\">") 238 fmt.Fprintln(writer, "<b>Logging output:</b>") 239 fmt.Fprintln(writer, "<pre>") 240 io.Copy(writer, buildLog) 241 fmt.Fprintln(writer, "</pre>") 242 fmt.Fprintln(writer, "</font>") 243 } 244 245 func (packager *packagerType) WriteHtml(writer io.Writer) { 246 fmt.Fprintf(writer, "Clean command: <code>%s</code><br>\n", 247 strings.Join(packager.CleanCommand, " ")) 248 fmt.Fprintf(writer, "Install command: <code>%s</code><br>\n", 249 strings.Join(packager.InstallCommand, " ")) 250 fmt.Fprintf(writer, "List command: <code>%s</code><br>\n", 251 strings.Join(packager.ListCommand.ArgList, " ")) 252 if packager.ListCommand.SizeMultiplier > 1 { 253 fmt.Fprintf(writer, "List command size multiplier: %d<br>\n", 254 packager.ListCommand.SizeMultiplier) 255 } 256 fmt.Fprintf(writer, "Remove command: <code>%s</code><br>\n", 257 strings.Join(packager.RemoveCommand, " ")) 258 fmt.Fprintf(writer, "Update command: <code>%s</code><br>\n", 259 strings.Join(packager.UpdateCommand, " ")) 260 fmt.Fprintf(writer, "Upgrade command: <code>%s</code><br>\n", 261 strings.Join(packager.UpgradeCommand, " ")) 262 if len(packager.Verbatim) > 0 { 263 fmt.Fprintln(writer, "Verbatim lines:<br>") 264 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 265 libjson.WriteWithIndent(writer, " ", packager.Verbatim) 266 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 267 } 268 fmt.Fprintln(writer, "Package installer script:<br>") 269 fmt.Fprintf(writer, "<pre style=\"%s\">\n", codeStyle) 270 packager.writePackageInstallerContents(writer) 271 fmt.Fprintln(writer, "</pre><p style=\"clear: both;\">") 272 }