github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/cmd/snap/cmd_debug_seeding.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"time"
    27  
    28  	"github.com/jessevdk/go-flags"
    29  
    30  	"github.com/snapcore/snapd/interfaces"
    31  )
    32  
    33  type cmdSeeding struct {
    34  	clientMixin
    35  	unicodeMixin
    36  }
    37  
    38  func init() {
    39  	cmd := addDebugCommand("seeding",
    40  		"(internal) obtain seeding and preseeding details",
    41  		"(internal) obtain seeding and preseeding details",
    42  		func() flags.Commander {
    43  			return &cmdSeeding{}
    44  		}, nil, nil)
    45  	cmd.hidden = true
    46  }
    47  
    48  func (x *cmdSeeding) Execute(args []string) error {
    49  	esc := x.getEscapes()
    50  
    51  	if len(args) > 0 {
    52  		return ErrExtraArgs
    53  	}
    54  	var resp struct {
    55  		Seeded           bool       `json:"seeded,omitempty"`
    56  		Preseeded        bool       `json:"preseeded,omitempty"`
    57  		PreseedStartTime *time.Time `json:"preseed-start-time,omitempty"`
    58  		PreseedTime      *time.Time `json:"preseed-time,omitempty"`
    59  		SeedStartTime    *time.Time `json:"seed-start-time,omitempty"`
    60  		SeedRestartTime  *time.Time `json:"seed-restart-time,omitempty"`
    61  		SeedTime         *time.Time `json:"seed-time,omitempty"`
    62  		// use json.RawMessage to delay unmarshal'ing to the interfaces pkg
    63  		PreseedSystemKey     *json.RawMessage `json:"preseed-system-key,omitempty"`
    64  		SeedRestartSystemKey *json.RawMessage `json:"seed-restart-system-key,omitempty"`
    65  
    66  		SeedError string `json:"seed-error,omitempty"`
    67  	}
    68  	if err := x.client.DebugGet("seeding", &resp, nil); err != nil {
    69  		return err
    70  	}
    71  
    72  	w := tabWriter()
    73  
    74  	// show seeded and preseeded keys
    75  	fmt.Fprintf(w, "seeded:\t%v\n", resp.Seeded)
    76  	if resp.SeedError != "" {
    77  		// print seed-error
    78  		termWidth, _ := termSize()
    79  		termWidth -= 3
    80  		if termWidth > 100 {
    81  			// any wider than this and it gets hard to read
    82  			termWidth = 100
    83  		}
    84  		fmt.Fprintln(w, "seed-error: |")
    85  		// XXX: reuse/abuse
    86  		printDescr(w, resp.SeedError, termWidth)
    87  	}
    88  
    89  	fmt.Fprintf(w, "preseeded:\t%v\n", resp.Preseeded)
    90  
    91  	// calculate the time spent preseeding (if preseeded) and seeding
    92  	// for the preseeded case, we use the seed-restart-time as the start time
    93  	// to show how long we spent only after booting the preseeded image
    94  
    95  	// if we are missing time values, we will default to showing "-" for the
    96  	// duration
    97  	seedDuration := esc.dash
    98  	if resp.Preseeded {
    99  		if resp.PreseedTime != nil && resp.PreseedStartTime != nil {
   100  			preseedDuration := resp.PreseedTime.Sub(*resp.PreseedStartTime).Round(time.Millisecond)
   101  			fmt.Fprintf(w, "image-preseeding:\t%v\n", preseedDuration)
   102  		} else {
   103  			fmt.Fprintf(w, "image-preseeding:\t%s\n", esc.dash)
   104  		}
   105  
   106  		if resp.SeedTime != nil && resp.SeedRestartTime != nil {
   107  			seedDuration = fmt.Sprintf("%v", resp.SeedTime.Sub(*resp.SeedRestartTime).Round(time.Millisecond))
   108  		}
   109  	} else if resp.SeedTime != nil && resp.SeedStartTime != nil {
   110  		seedDuration = fmt.Sprintf("%v", resp.SeedTime.Sub(*resp.SeedStartTime).Round(time.Millisecond))
   111  	}
   112  	fmt.Fprintf(w, "seed-completion:\t%s\n", seedDuration)
   113  
   114  	// we flush the tabwriter now because if we have more output, it will be
   115  	// the system keys, which are JSON and thus will never display cleanly in
   116  	// line with the other keys we did above
   117  	w.Flush()
   118  
   119  	// only compare system-keys if preseeded and the system-keys exist
   120  	// they might not exist if this command is used on a system that was
   121  	// preseeded with an older version of snapd, i.e. while this feature is
   122  	// being rolled out, we may be preseeding images via old snapd deb, but with
   123  	// new snapd snap
   124  	if resp.Preseeded && resp.SeedRestartSystemKey != nil && resp.PreseedSystemKey != nil {
   125  		// only show them if they don't match, so first unmarshal them so we can
   126  		// properly compare them
   127  
   128  		// we use raw json messages here so that the interfaces pkg can do the
   129  		// real unmarshalling to a real systemKey interface{} that can be
   130  		// compared with SystemKeysMatch, if we had instead unmarshalled here,
   131  		// we would have to remarshal the map[string]interface{} we got above
   132  		// and then pass those bytes back to the interfaces pkg which is awkward
   133  		seedSk, err := interfaces.UnmarshalJSONSystemKey(bytes.NewReader(*resp.SeedRestartSystemKey))
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		preseedSk, err := interfaces.UnmarshalJSONSystemKey(bytes.NewReader(*resp.PreseedSystemKey))
   139  		if err != nil {
   140  			return err
   141  		}
   142  
   143  		match, err := interfaces.SystemKeysMatch(preseedSk, seedSk)
   144  		if err != nil {
   145  			return err
   146  		}
   147  		if !match {
   148  			// mismatch, display the different keys
   149  			var preseedSkJSON, seedRestartSkJSON bytes.Buffer
   150  			json.Indent(&preseedSkJSON, *resp.PreseedSystemKey, "", "  ")
   151  			fmt.Fprintf(Stdout, "preseed-system-key: ")
   152  			preseedSkJSON.WriteTo(Stdout)
   153  			fmt.Fprintln(Stdout, "")
   154  
   155  			json.Indent(&seedRestartSkJSON, *resp.SeedRestartSystemKey, "", "  ")
   156  			fmt.Fprintf(Stdout, "seed-restart-system-key: ")
   157  			seedRestartSkJSON.WriteTo(Stdout)
   158  			fmt.Fprintln(Stdout, "")
   159  		}
   160  	}
   161  
   162  	return nil
   163  }