func (p *taskPlugin) setupDisplay() error {
	// Setup display if not disabled
	if p.opts.DisableDisplay {
		return nil
	}
	debug("Setting up interactive display")

	// Create display server
	p.displayServer = NewDisplayServer(
		p.sandbox, p.log.WithField("interactive", "display"),
	)
	u := p.context.AttachWebHook(p.displayServer)
	p.displaysURL = u
	p.displaySocketURL = urlProtocolToWebsocket(u)

	query := url.Values{}
	query.Set("v", "1")
	query.Set("taskId", p.context.TaskID)
	query.Set("runId", fmt.Sprintf("%d", p.context.RunID))
	query.Set("socketUrl", p.displaySocketURL)
	query.Set("displaysUrl", p.displaysURL)
	// TODO: Make this an option the engine can specify in ListDisplays
	//       Probably requires changing display list result to contain websocket
	//       URLs. Hence, introducing v=2, so leaving it for later.
	query.Set("shared", "true")

	return runtime.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     p.opts.ArtifactPrefix + "display.html",
		Mimetype: "text/html",
		URL:      p.parent.config.DisplayToolURL + "?" + query.Encode(),
		Expires:  p.context.TaskInfo.Deadline,
	}, p.context)
}
func (p *taskPlugin) setupShell() error {
	// Setup shell if not disabled
	if p.opts.DisableShell {
		return nil
	}
	debug("Setting up interactive shell")

	// Create shell server and get a URL to reach it
	p.shellServer = NewShellServer(
		p.sandbox.NewShell, p.log.WithField("interactive", "shell"),
	)
	u := p.context.AttachWebHook(p.shellServer)
	p.shellURL = urlProtocolToWebsocket(u)

	query := url.Values{}
	query.Set("v", "2")
	query.Set("taskId", p.context.TaskID)
	query.Set("runId", fmt.Sprintf("%d", p.context.RunID))
	query.Set("socketUrl", p.shellURL)

	return runtime.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     p.opts.ArtifactPrefix + "shell.html",
		Mimetype: "text/html",
		URL:      p.parent.config.ShellToolURL + "?" + query.Encode(),
		Expires:  p.context.TaskInfo.Deadline,
	}, p.context)
}
func (tp *taskPlugin) Prepare(context *runtime.TaskContext) error {
	tp.context = context

	tp.url = tp.context.AttachWebHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// TODO (garndt): add support for range headers.  Might not be used at all currently
		logReader, err := tp.context.NewLogReader()
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte("Error opening up live log"))
			return
		}
		defer logReader.Close()

		// Get an HTTP flusher if supported in the current context, or wrap in
		// a NopFlusher, if flushing isn't available.
		wf, ok := w.(ioext.WriteFlusher)
		if !ok {
			wf = ioext.NopFlusher(w)
		}

		ioext.CopyAndFlush(wf, logReader, 100*time.Millisecond)
	}))

	err := runtime.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     "public/logs/live.log",
		Mimetype: "text/plain",
		URL:      tp.url,
		Expires:  tp.context.TaskInfo.Expires,
	}, tp.context)
	if err != nil {
		tp.context.LogError(fmt.Sprintf("Could not initialize live log plugin. Error: %s", err))
	}

	return err
}
func (tp *taskPlugin) Finished(success bool) error {
	file, err := tp.context.ExtractLog()
	if err != nil {
		return err
	}
	defer file.Close()

	tempFile, err := tp.environment.TemporaryStorage.NewFile()
	if err != nil {
		return err
	}

	defer tempFile.Close()

	zip := gzip.NewWriter(tempFile)
	if _, err = io.Copy(zip, file); err != nil {
		return err
	}

	if err = zip.Close(); err != nil {
		return err
	}

	_, err = tempFile.Seek(0, 0)
	if err != nil {
		return err
	}

	err = runtime.UploadS3Artifact(runtime.S3Artifact{
		Name:     "public/logs/live_backing.log",
		Mimetype: "text/plain",
		Expires:  tp.context.TaskInfo.Expires,
		Stream:   tempFile,
		AdditionalHeaders: map[string]string{
			"Content-Encoding": "gzip",
		},
	}, tp.context)

	if err != nil {
		return err
	}

	backingURL := fmt.Sprintf("https://queue.taskcluster.net/v1/task/%s/runs/%d/artifacts/public/logs/live_backing.log", tp.context.TaskInfo.TaskID, tp.context.TaskInfo.RunID)
	err = runtime.CreateRedirectArtifact(runtime.RedirectArtifact{
		Name:     "public/logs/live.log",
		Mimetype: "text/plain",
		URL:      backingURL,
		Expires:  tp.context.TaskInfo.Expires,
	}, tp.context)
	if err != nil {
		tp.log.Error(err)
		return err
	}

	return nil
}