[WIP] Add CI for building deb,rpm installers
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func confirm(msg string) bool {
|
||||
fmt.Printf("%s: [y|n] ", msg)
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
ok := scanner.Scan()
|
||||
if ok && strings.TrimSpace(scanner.Text()) == "y" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,58 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/portmaster/base/updater"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var (
|
||||
registry *updater.ResourceRegistry
|
||||
distDir string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "updatemgr",
|
||||
Short: "A simple tool to assist in the update and release process",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Check if the distribution directory exists.
|
||||
absDistPath, err := filepath.Abs(distDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path of distribution directory: %w", err)
|
||||
}
|
||||
_, err = os.Stat(absDistPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to access distribution directory: %w", err)
|
||||
}
|
||||
|
||||
registry = &updater.ResourceRegistry{}
|
||||
err = registry.Initialize(utils.NewDirStructure(absDistPath, 0o0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = registry.ScanStorage("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
var binaryMap = map[string]updates.Artifact{
|
||||
"portmaster-core": {
|
||||
Platform: "linux_amd64",
|
||||
},
|
||||
"portmaster-core.exe": {
|
||||
Platform: "windows_amd64",
|
||||
},
|
||||
"portmaster-kext.sys": {
|
||||
Platform: "windows_amd64",
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
flags := rootCmd.PersistentFlags()
|
||||
flags.StringVar(&distDir, "dist-dir", "dist", "Set the distribution directory. Falls back to ./dist if available.")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
dir := flag.String("dir", "", "path to the directory that contains the artifacts")
|
||||
name := flag.String("name", "", "name of the bundle")
|
||||
version := flag.String("version", "", "version of the bundle")
|
||||
|
||||
flag.Parse()
|
||||
if *dir == "" {
|
||||
fmt.Fprintf(os.Stderr, "-dir parameter is required\n")
|
||||
return
|
||||
}
|
||||
if *name == "" {
|
||||
fmt.Fprintf(os.Stderr, "-name parameter is required\n")
|
||||
return
|
||||
}
|
||||
|
||||
bundle, err := updates.GenerateBundleFromDir(*name, *version, binaryMap, *dir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to generate bundle: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
bundleStr, err := json.MarshalIndent(&bundle, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to marshal bundle: %s\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s", bundleStr)
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(purgeCmd)
|
||||
}
|
||||
|
||||
var purgeCmd = &cobra.Command{
|
||||
Use: "purge",
|
||||
Short: "Remove old resource versions that are superseded by at least three versions",
|
||||
RunE: purge,
|
||||
}
|
||||
|
||||
func purge(cmd *cobra.Command, args []string) error {
|
||||
log.SetLogLevel(log.TraceLevel)
|
||||
err := log.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to start logging: %s\n", err)
|
||||
}
|
||||
defer log.Shutdown()
|
||||
|
||||
registry.SelectVersions()
|
||||
registry.Purge(3)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/portmaster/base/updater"
|
||||
)
|
||||
|
||||
var (
|
||||
releaseCmd = &cobra.Command{
|
||||
Use: "release",
|
||||
Short: "Release scans the distribution directory and creates registry indexes and the symlink structure",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: release,
|
||||
}
|
||||
preReleaseCmd = &cobra.Command{
|
||||
Use: "prerelease",
|
||||
Short: "Stage scans the specified directory and loads the indexes - it then creates a staging index with all files newer than the stable and beta indexes",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: release,
|
||||
}
|
||||
preReleaseFrom string
|
||||
resetPreReleases bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(releaseCmd)
|
||||
rootCmd.AddCommand(preReleaseCmd)
|
||||
|
||||
preReleaseCmd.Flags().StringVar(&preReleaseFrom, "from", "", "Make a pre-release based on the given channel")
|
||||
_ = preReleaseCmd.MarkFlagRequired("from")
|
||||
preReleaseCmd.Flags().BoolVar(&resetPreReleases, "reset", false, "Reset pre-release assets")
|
||||
}
|
||||
|
||||
func release(cmd *cobra.Command, args []string) error {
|
||||
channel := args[0]
|
||||
|
||||
// Check if we want to reset instead.
|
||||
if resetPreReleases {
|
||||
return removeFilesFromIndex(getChannelVersions(preReleaseFrom, true))
|
||||
}
|
||||
|
||||
// Write new index.
|
||||
err := writeIndex(
|
||||
channel,
|
||||
getChannelVersions(preReleaseFrom, false),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only when doing a release:
|
||||
if preReleaseFrom == "" {
|
||||
// Create symlinks to latest stable versions.
|
||||
if !confirm("\nDo you want to write latest symlinks?") {
|
||||
fmt.Println("aborted...")
|
||||
return nil
|
||||
}
|
||||
symlinksDir := registry.StorageDir().ChildDir("latest", 0o755)
|
||||
err = registry.CreateSymlinks(symlinksDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("written latest symlinks")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeIndex(channel string, versions map[string]string) error {
|
||||
// Create new index file.
|
||||
indexFile := &updater.IndexFile{
|
||||
Channel: channel,
|
||||
Published: time.Now().UTC().Round(time.Second),
|
||||
Releases: versions,
|
||||
}
|
||||
|
||||
// Export versions and format them.
|
||||
confirmData, err := json.MarshalIndent(indexFile, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build index paths.
|
||||
oldIndexPath := filepath.Join(registry.StorageDir().Path, channel+".json")
|
||||
newIndexPath := filepath.Join(registry.StorageDir().Path, channel+".v2.json")
|
||||
|
||||
// Print preview.
|
||||
fmt.Printf("%s\n%s\n%s\n\n", channel, oldIndexPath, newIndexPath)
|
||||
fmt.Println(string(confirmData))
|
||||
|
||||
// Ask for confirmation.
|
||||
if !confirm("\nDo you want to write this index?") {
|
||||
fmt.Println("aborted...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write indexes.
|
||||
err = writeAsJSON(oldIndexPath, versions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write %s: %w", oldIndexPath, err)
|
||||
}
|
||||
err = writeAsJSON(newIndexPath, indexFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write %s: %w", newIndexPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeAsJSON(path string, data any) error {
|
||||
// Marshal to JSON.
|
||||
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write to disk.
|
||||
err = os.WriteFile(path, jsonData, 0o0644) //nolint:gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("written %s\n", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeFilesFromIndex(versions map[string]string) error {
|
||||
// Print preview.
|
||||
fmt.Println("To be deleted:")
|
||||
for _, filePath := range versions {
|
||||
fmt.Println(filePath)
|
||||
}
|
||||
|
||||
// Ask for confirmation.
|
||||
if !confirm("\nDo you want to delete these files?") {
|
||||
fmt.Println("aborted...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete files.
|
||||
for _, filePath := range versions {
|
||||
err := os.Remove(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Println("deleted")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getChannelVersions(prereleaseFrom string, storagePath bool) map[string]string {
|
||||
if prereleaseFrom != "" {
|
||||
registry.AddIndex(updater.Index{
|
||||
Path: prereleaseFrom + ".json",
|
||||
PreRelease: false,
|
||||
})
|
||||
err := registry.LoadIndexes(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort all versions.
|
||||
registry.SelectVersions()
|
||||
export := registry.Export()
|
||||
|
||||
// Go through all versions and save the highest version, if not stable or beta.
|
||||
versions := make(map[string]string)
|
||||
for _, rv := range export {
|
||||
highestVersion := rv.Versions[0]
|
||||
|
||||
// Ignore versions that are in the reference release channel.
|
||||
if highestVersion.CurrentRelease {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add highest version of matching release channel.
|
||||
if storagePath {
|
||||
versions[rv.Identifier] = rv.GetFile().Path()
|
||||
} else {
|
||||
versions[rv.Identifier] = highestVersion.VersionNumber
|
||||
}
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(scanCmd)
|
||||
}
|
||||
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan the specified directory and print the result",
|
||||
RunE: scan,
|
||||
}
|
||||
|
||||
func scan(cmd *cobra.Command, args []string) error {
|
||||
// Reset and rescan.
|
||||
registry.ResetResources()
|
||||
err := registry.ScanStorage("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Export latest versions.
|
||||
data, err := json.MarshalIndent(exportSelected(true), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Print them.
|
||||
fmt.Println(string(data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportSelected(preReleases bool) map[string]string {
|
||||
registry.SetUsePreReleases(preReleases)
|
||||
registry.SelectVersions()
|
||||
export := registry.Export()
|
||||
|
||||
versions := make(map[string]string)
|
||||
for _, rv := range export {
|
||||
versions[rv.Identifier] = rv.SelectedVersion.VersionNumber
|
||||
}
|
||||
return versions
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/jess"
|
||||
"github.com/safing/jess/filesig"
|
||||
"github.com/safing/jess/truststores"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(signCmd)
|
||||
|
||||
// Required argument: envelope
|
||||
signCmd.PersistentFlags().StringVarP(&envelopeName, "envelope", "", "",
|
||||
"specify envelope name used for signing",
|
||||
)
|
||||
_ = signCmd.MarkFlagRequired("envelope")
|
||||
|
||||
// Optional arguments: verbose, tsdir, tskeyring
|
||||
signCmd.PersistentFlags().BoolVarP(&signVerbose, "verbose", "v", false,
|
||||
"enable verbose output",
|
||||
)
|
||||
signCmd.PersistentFlags().StringVarP(&trustStoreDir, "tsdir", "", "",
|
||||
"specify a truststore directory (default loaded from JESS_TS_DIR env variable)",
|
||||
)
|
||||
signCmd.PersistentFlags().StringVarP(&trustStoreKeyring, "tskeyring", "", "",
|
||||
"specify a truststore keyring namespace (default loaded from JESS_TS_KEYRING env variable) - lower priority than tsdir",
|
||||
)
|
||||
|
||||
// Subcommand for signing indexes.
|
||||
signCmd.AddCommand(signIndexCmd)
|
||||
}
|
||||
|
||||
var (
|
||||
signCmd = &cobra.Command{
|
||||
Use: "sign",
|
||||
Short: "Sign resources",
|
||||
RunE: sign,
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
signIndexCmd = &cobra.Command{
|
||||
Use: "index",
|
||||
Short: "Sign indexes",
|
||||
RunE: signIndex,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
|
||||
envelopeName string
|
||||
signVerbose bool
|
||||
)
|
||||
|
||||
func sign(cmd *cobra.Command, args []string) error {
|
||||
// Setup trust store.
|
||||
trustStore, err := setupTrustStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get envelope.
|
||||
signingEnvelope, err := trustStore.GetEnvelope(envelopeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all resources and iterate over all versions.
|
||||
export := registry.Export()
|
||||
var verified, signed, fails int
|
||||
for _, rv := range export {
|
||||
for _, version := range rv.Versions {
|
||||
file := version.GetFile()
|
||||
|
||||
// Check if there is an existing signature.
|
||||
_, err := os.Stat(file.Path() + filesig.Extension)
|
||||
switch {
|
||||
case err == nil || errors.Is(err, fs.ErrExist):
|
||||
// If the file exists, just verify.
|
||||
fileData, err := filesig.VerifyFile(
|
||||
file.Path(),
|
||||
file.Path()+filesig.Extension,
|
||||
file.SigningMetadata(),
|
||||
trustStore,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("[FAIL] signature error for %s: %s\n", file.Path(), err)
|
||||
fails++
|
||||
} else {
|
||||
if signVerbose {
|
||||
fmt.Printf("[ OK ] valid signature for %s: signed by %s\n", file.Path(), getSignedByMany(fileData, trustStore))
|
||||
}
|
||||
verified++
|
||||
}
|
||||
|
||||
case errors.Is(err, fs.ErrNotExist):
|
||||
// Attempt to sign file.
|
||||
fileData, err := filesig.SignFile(
|
||||
file.Path(),
|
||||
file.Path()+filesig.Extension,
|
||||
file.SigningMetadata(),
|
||||
signingEnvelope,
|
||||
trustStore,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("[FAIL] failed to sign %s: %s\n", file.Path(), err)
|
||||
fails++
|
||||
} else {
|
||||
fmt.Printf("[SIGN] signed %s with %s\n", file.Path(), getSignedBySingle(fileData, trustStore))
|
||||
signed++
|
||||
}
|
||||
|
||||
default:
|
||||
// File access error.
|
||||
fmt.Printf("[FAIL] failed to access %s: %s\n", file.Path(), err)
|
||||
fails++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if verified > 0 {
|
||||
fmt.Printf("[STAT] verified %d files\n", verified)
|
||||
}
|
||||
if signed > 0 {
|
||||
fmt.Printf("[STAT] signed %d files\n", signed)
|
||||
}
|
||||
if fails > 0 {
|
||||
return fmt.Errorf("signing or verification failed on %d files", fails)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func signIndex(cmd *cobra.Command, args []string) error {
|
||||
// Setup trust store.
|
||||
trustStore, err := setupTrustStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get envelope.
|
||||
signingEnvelope, err := trustStore.GetEnvelope(envelopeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Resolve globs.
|
||||
files := make([]string, 0, len(args))
|
||||
for _, arg := range args {
|
||||
matches, err := filepath.Glob(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files = append(files, matches...)
|
||||
}
|
||||
|
||||
// Go through all files.
|
||||
var verified, signed, fails int
|
||||
for _, file := range files {
|
||||
sigFile := file + filesig.Extension
|
||||
|
||||
// Ignore matches for the signatures.
|
||||
if strings.HasSuffix(file, filesig.Extension) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if there is an existing signature.
|
||||
_, err := os.Stat(sigFile)
|
||||
switch {
|
||||
case err == nil || errors.Is(err, fs.ErrExist):
|
||||
// If the file exists, just verify.
|
||||
fileData, err := filesig.VerifyFile(
|
||||
file,
|
||||
sigFile,
|
||||
nil,
|
||||
trustStore,
|
||||
)
|
||||
if err == nil {
|
||||
if signVerbose {
|
||||
fmt.Printf("[ OK ] valid signature for %s: signed by %s\n", file, getSignedByMany(fileData, trustStore))
|
||||
}
|
||||
verified++
|
||||
|
||||
// Indexes are expected to change, so just sign the index again if verification fails.
|
||||
continue
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case errors.Is(err, fs.ErrNotExist):
|
||||
// Attempt to sign file.
|
||||
fileData, err := filesig.SignFile(
|
||||
file,
|
||||
sigFile,
|
||||
nil,
|
||||
signingEnvelope,
|
||||
trustStore,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("[FAIL] failed to sign %s: %s\n", file, err)
|
||||
fails++
|
||||
} else {
|
||||
fmt.Printf("[SIGN] signed %s with %s\n", file, getSignedBySingle(fileData, trustStore))
|
||||
signed++
|
||||
}
|
||||
|
||||
default:
|
||||
// File access error.
|
||||
fmt.Printf("[FAIL] failed to access %s: %s\n", sigFile, err)
|
||||
fails++
|
||||
}
|
||||
}
|
||||
|
||||
if verified > 0 {
|
||||
fmt.Printf("[STAT] verified %d files", verified)
|
||||
}
|
||||
if signed > 0 {
|
||||
fmt.Printf("[STAT] signed %d files", signed)
|
||||
}
|
||||
if fails > 0 {
|
||||
return fmt.Errorf("signing failed on %d files", fails)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
trustStoreDir string
|
||||
trustStoreKeyring string
|
||||
)
|
||||
|
||||
func setupTrustStore() (trustStore truststores.ExtendedTrustStore, err error) {
|
||||
// Get trust store directory.
|
||||
if trustStoreDir == "" {
|
||||
trustStoreDir, _ = os.LookupEnv("JESS_TS_DIR")
|
||||
if trustStoreDir == "" {
|
||||
trustStoreDir, _ = os.LookupEnv("JESS_TSDIR")
|
||||
}
|
||||
}
|
||||
if trustStoreDir != "" {
|
||||
trustStore, err = truststores.NewDirTrustStore(trustStoreDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get trust store keyring.
|
||||
if trustStore == nil {
|
||||
if trustStoreKeyring == "" {
|
||||
trustStoreKeyring, _ = os.LookupEnv("JESS_TS_KEYRING")
|
||||
if trustStoreKeyring == "" {
|
||||
trustStoreKeyring, _ = os.LookupEnv("JESS_TSKEYRING")
|
||||
}
|
||||
}
|
||||
if trustStoreKeyring != "" {
|
||||
trustStore, err = truststores.NewKeyringTrustStore(trustStoreKeyring)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Truststore is mandatory.
|
||||
if trustStore == nil {
|
||||
return nil, errors.New("no truststore configured, please pass arguments or use env variables")
|
||||
}
|
||||
|
||||
return trustStore, nil
|
||||
}
|
||||
|
||||
func getSignedByMany(fds []*filesig.FileData, trustStore jess.TrustStore) string {
|
||||
signedBy := make([]string, 0, len(fds))
|
||||
for _, fd := range fds {
|
||||
if sig := fd.Signature(); sig != nil {
|
||||
for _, seal := range sig.Signatures {
|
||||
if signet, err := trustStore.GetSignet(seal.ID, true); err == nil {
|
||||
signedBy = append(signedBy, fmt.Sprintf("%s (%s)", signet.Info.Name, seal.ID))
|
||||
} else {
|
||||
signedBy = append(signedBy, seal.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(signedBy, " and ")
|
||||
}
|
||||
|
||||
func getSignedBySingle(fd *filesig.FileData, trustStore jess.TrustStore) string {
|
||||
if sig := fd.Signature(); sig != nil {
|
||||
signedBy := make([]string, 0, len(sig.Signatures))
|
||||
for _, seal := range sig.Signatures {
|
||||
if signet, err := trustStore.GetSignet(seal.ID, true); err == nil {
|
||||
signedBy = append(signedBy, fmt.Sprintf("%s (%s)", signet.Info.Name, seal.ID))
|
||||
} else {
|
||||
signedBy = append(signedBy, seal.ID)
|
||||
}
|
||||
}
|
||||
return strings.Join(signedBy, " and ")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user