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  }