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  }