hancock

Check-in [88140a8c62]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:cmd/hancock: publish from local to remote store refactor testimony and publish operations, testimony to local (or remote) store, later publish from local to remote
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 88140a8c62832e465df5f75545a43b6119627711f68f8b2ae583982d26b4f644
User & Date: dnc 2020-01-03 17:24:53
Context
2020-01-03
17:27
cmd/hancock: no longer publishing via ipfs check-in: d2f81cb81e user: dnc tags: trunk
17:24
cmd/hancock: publish from local to remote store refactor testimony and publish operations, testimony to local (or remote) store, later publish from local to remote check-in: 88140a8c62 user: dnc tags: trunk
17:20
store: introduce Iterable interface Allow processing of every testimony in local store. check-in: 2009980ac3 user: dnc tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to cmd/hancock/publish.go.

1
2
3
4
5
6
7
8
..
19
20
21
22
23
24
25
26
27
28

29
30
31
32
33
34
35
36

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

93
94
95

96
97
98
99
100
101
102
103
104
105
106
107

108
109
110

111

112
113
114
115


116
117
118





119
120
121

122
123
124
125
126
127
128
129
130
131

132
133
134
135

136


137
138


139
140
141
142
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

186
187
188
189
190
191
192
// Copyright (C) 2019  David N. Cohen

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
................................................................................
//    hancock publish
//
// This operation published the files produced by `hancock testimony`.
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"

	"net/http"
	"net/url"
	"os"
	"path/filepath"

	"github.com/pkg/errors"
	"src.d10.dev/command"
	"src.d10.dev/hancock/model"

)

var (
	// Flags that may be used by both publish and testimony operations.

	storeFlag command.StringSet
)

func validateStoreFlag(def string) error {

	if len(storeFlag) == 0 && def != "" {
		storeFlag = append(storeFlag, def)
	}
	for _, val := range storeFlag {
		if val == "" {
			continue
		}
		u, err := url.Parse(val)
		if err != nil {
			command.Error(errors.Wrapf(err, "bad index URL (%s)", val))
			command.Exit()
		}
		switch u.Scheme {
		case "http", "https", "file":
			// supported scheme
		default:
			command.Errorf("bad index (%q): scheme must be http(s):// or file://", val)
			command.Exit()
		}
	}
	return nil
}

func init() {
	command.RegisterOperation(command.Operation{
		Handler:     publishMain,
		Name:        "publish",
		Syntax:      "publish [-store=<url>] [in=<directory] [testimony ...]",
		Description: "Publish signed manifest to an index. Expects signed activity on stdin, as encoded by `hancock-sign`.",

	})
}

func publishMain() error {
	cfg, err := command.Config()
	command.Check(err)

	// defaults
	cfgTop := cfg.Section("")
	cfgOp := cfg.Section("publish")

	defaultStore := cfgOp.Key("store").MustString(cfgTop.Key("store").MustString("https://hancock.beyondcentral.com"))
	defaultDirectory := cfgOp.Key("testimony").MustString(cfgTop.Key("testimony").String())

	// args
	command.OperationFlagSet.Var(&storeFlag, "store", fmt.Sprintf("URL of testimony store (default %q)", defaultStore))
	inFlag := command.OperationFlagSet.String("in", defaultDirectory, "directory containing testimony files")


	// parse flags
	err = command.OperationFlagSet.Parse(command.Args()[1:])

	if err != nil {
		return err
	}

	// validate flags
	err = validateStoreFlag(defaultStore)
	if err != nil {
		return err
	}
	if len(storeFlag) == 0 {
		return errors.New("Storage URL required. Use flag `-store <URL>` or see configuration file.")
	}


	if len(command.OperationFlagSet.Args()) == 0 && *inFlag == "" {
		return errors.New("Testimony required.  Use `-in <directory>` or specify testimony files.")

	}

	if *inFlag != "" {
		inStat, err := os.Stat(*inFlag)
		if os.IsNotExist(err) {
			return fmt.Errorf("Input directory does not exist (%q).", *inFlag)


		}
		if !inStat.IsDir() {
			return fmt.Errorf("Not a directory (%q).", *inFlag)





		}
	}


	in := command.OperationFlagSet.Args()
	if len(in) == 0 {
		in = append(in, *inFlag)
	}

	// TODO(dnc): concurrency
	for _, fileOrDir := range in {
		count := 0
		err = filepath.Walk(fileOrDir, func(filepath string, info os.FileInfo, err error) error {
			if err != nil {

				return err
			}
			if info.IsDir() {
				return nil // noop, continue walk (we only process files, not directories)

			}



			f, err := os.Open(filepath)


			if err != nil {
				return err
			}
			defer f.Close()


			dec := json.NewDecoder(f)
			var testimony fileTestimony
			err = dec.Decode(&testimony)
			if err != nil {
				return err
			}
			err = testimony.FileManifest.Check()
			if err != nil {
				return err
			}

			// validate testimony here?
			stored := false
			for _, storeURL := range storeFlag {
				err = publishTestimonyToStore(storeURL, testimony)
				if err != nil {
					command.Error(err)
				} else {
					stored = true
				}
			}
			if stored {
				count++
			}
			return err
		})
		command.V(1).Infof("Published %d items from %q.", count, fileOrDir)
		if err != nil {
			command.Error(err)
		}
	}

	return nil
}

func publishTestimonyToIPFS(ipfsURL string, ft fileTestimony) error {
	err := ipfsAdd(ipfsURL, ft.Bytes(), ft.FileName())
	return err
}

// publish testimony either to local filesystem or via POST to online index.
func publishTestimonyToStore(storeURL string, ft fileTestimony) error {

	u, err := url.Parse(storeURL)
	if err != nil {
		return err
	}

	err = ft.Check()
	if err != nil {
|







 







<


>








>


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




|
<
>












<


<
<
>


<
>





|



<
<
|
>

|
<
>

>
|
|
|
|
>
>

<
<
>
>
>
>
>



>
|
<
|
<
<
<
<
<
<
|
>
|
|
<
<
>
|
>
>

<
>
>



<

>
|
<
<
<
<
<
<




|
|
<
<
<
<
<
<
<
<
<
<
<
|

<





|
|
<
<
<
|



|
>







1
2
3
4
5
6
7
8
..
19
20
21
22
23
24
25

26
27
28
29
30
31
32
33
34
35
36
37
38
39































40
41
42
43
44

45
46
47
48
49
50
51
52
53
54
55
56
57

58
59


60
61
62

63
64
65
66
67
68
69
70
71
72


73
74
75
76

77
78
79
80
81
82
83
84
85
86


87
88
89
90
91
92
93
94
95
96

97






98
99
100
101


102
103
104
105
106

107
108
109
110
111

112
113
114






115
116
117
118
119
120











121
122

123
124
125
126
127
128
129



130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright (C) 2019, 2020  David N. Cohen

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
................................................................................
//    hancock publish
//
// This operation published the files produced by `hancock testimony`.
package main

import (
	"bytes"

	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"

	"github.com/pkg/errors"
	"src.d10.dev/command"
	"src.d10.dev/hancock/model"
	"src.d10.dev/hancock/store"
)
































func init() {
	command.RegisterOperation(command.Operation{
		Handler:     publishMain,
		Name:        "publish",
		Syntax:      "publish [-store=<destination>] <source> [...]",

		Description: "Publish signed testimony from a local store to a remote store.  Use `hancock testimony` to populate local store.",
	})
}

func publishMain() error {
	cfg, err := command.Config()
	command.Check(err)

	// defaults
	cfgTop := cfg.Section("")
	cfgOp := cfg.Section("publish")

	defaultStore := cfgOp.Key("store").MustString(cfgTop.Key("store").MustString("https://hancock.beyondcentral.com"))


	// args


	storeFlag := command.OperationFlagSet.String("store", "defaultStore", fmt.Sprintf("URL of remote testimony store (default %q)", defaultStore))

	// parse flags

	err = command.ParseOperationFlagSet()
	if err != nil {
		return err
	}

	// validate flags
	remote, err := storeFromFlag(*storeFlag)
	if err != nil {
		return err
	}



	defer closeStore() // closes all results of storeFromFlag()

	if len(command.OperationFlagSet.Args()) == 0 {

		return errors.New("Local testimony store required.")
	}

	// first loop, validate that local store supports Iterable
	for _, arg := range command.OperationFlagSet.Args() {
		in, err := storeFromFlag(arg)
		if err != nil {
			command.Error(fmt.Errorf("failed to open local testimony store (%q): %w", arg, err))
			return nil
		}


		// input stores must implement Iterable (output does not need to)
		_, ok := in.(store.Iterable)
		if !ok {
			command.Errorf("Cannot publish from testimony store (%q): not iterable", arg)
			return nil
		}
	}

	pubs := 0
	for _, arg := range command.OperationFlagSet.Args() {

		tmp, err := storeFromFlag(arg)






		if err != nil {
			command.Error(fmt.Errorf("failed to open local testimony store (%q): %w", arg, err))
			return nil
		}


		in := tmp.(store.Iterable)

		err = in.ForEach(func(key model.TestimonyKey, testimony model.Testimony) error {
			// TODO(dnc): concurrency, for performance


			// sanity check (should not be needed)
			err := testimony.Check()
			if err != nil {
				return err
			}


			// publish testimony from local to remote
			k, err := remote.PutTestimony(testimony)






			if err != nil {
				return err
			}

			command.V(1).Infof("published %v to %v", k, remote)
			pubs++











			return nil
		})

		if err != nil {
			command.Error(err)
		}
	}

	command.Infof("published %d items to %q", pubs, remote)




	return nil
}

// publish testimony either to local filesystem or via POST to online index.
func XXXpublishTestimonyToStore(storeURL string, ft fileTestimony) error {
	log.Panic("replace calls to publishTestimonyToStore() with store.Store interface!")
	u, err := url.Parse(storeURL)
	if err != nil {
		return err
	}

	err = ft.Check()
	if err != nil {

Changes to cmd/hancock/testimony.go.

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
..
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
...
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh"

	"src.d10.dev/command"
	"src.d10.dev/hancock/model"
)

func init() {
................................................................................
	// defaults
	cfgTop := cfg.Section("")
	cfgTestimony := cfg.Section("testimony")

	defaultStore := cfgTestimony.Key("store").MustString(cfgTop.Key("store").String())

	// define args
	command.OperationFlagSet.Var(&storeFlag, "store", fmt.Sprintf("URL where testimony is saved/published (default %q)", defaultStore))
	noopFlag := command.OperationFlagSet.Bool("n", false, "do not save/publish")

	// parse args
	err = command.OperationFlagSet.Parse(command.Args()[1:])
	if err != nil {
		return err
	}

	// validate args
	err = validateStoreFlag(defaultStore)
	if err != nil {
		return err
	}
	if len(storeFlag) == 0 {
		return errors.New("Storage URL required. Use flag `-store <URL>` or see configuration file.")
	}
	defer closeStore() // cleanup; safe to call even if no stores initialized

	// decode input
	dec := json.NewDecoder(os.Stdin)

	// first input, the public key (corresponding to signing private key)
	var authorityEncoded model.Authority
................................................................................
			Authority: authorityEncoded,
			Content:   man.Content,
			Encoded:   rawMan,
			Signature: sig,
		}

		if !*noopFlag {
			//ft := fileTestimony{Testimony: testimony, FileManifest: man} // testimony with decoded manifest
			stored := false
			for _, storeURL := range storeFlag {
				if storeURL == "" {
					continue // explicit -index="" on command line
				}
				stor, err := storeFromFlag(storeURL)
				if err != nil {
					command.Errorf("failed to publish testimony (%q) to %q: %s", man.Path, storeURL, err)
					continue
				}
				tkey, err := stor.PutTestimony(testimony)
				if err != nil {
					command.Errorf("failed to publish testimony (%q) to %q: %s", man.Path, storeURL, err)
					continue
				}
				stored = true
				command.V(2).Infof("published testimony (%q) to %q; key: %v", man.Path, storeURL, tkey)
			}
			if stored {
				command.V(1).Infof("%q testimony published", man.Path)
			}
		}
	}

	return nil
}







<







 







|









|



<
<
<







 







<
<
<
<
<
<
<
<
<
<
<
|
|
|
|
|
<
<
<
<
|
<





26
27
28
29
30
31
32

33
34
35
36
37
38
39
..
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117



118
119
120
121
122
123
124
...
152
153
154
155
156
157
158











159
160
161
162
163




164

165
166
167
168
169
import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"


	"golang.org/x/crypto/ssh"

	"src.d10.dev/command"
	"src.d10.dev/hancock/model"
)

func init() {
................................................................................
	// defaults
	cfgTop := cfg.Section("")
	cfgTestimony := cfg.Section("testimony")

	defaultStore := cfgTestimony.Key("store").MustString(cfgTop.Key("store").String())

	// define args
	storeFlag := command.OperationFlagSet.String("store", defaultStore, "URL where testimony is saved/published")
	noopFlag := command.OperationFlagSet.Bool("n", false, "do not save/publish")

	// parse args
	err = command.OperationFlagSet.Parse(command.Args()[1:])
	if err != nil {
		return err
	}

	// validate args
	remote, err := storeFromFlag(*storeFlag)
	if err != nil {
		return err
	}



	defer closeStore() // cleanup; safe to call even if no stores initialized

	// decode input
	dec := json.NewDecoder(os.Stdin)

	// first input, the public key (corresponding to signing private key)
	var authorityEncoded model.Authority
................................................................................
			Authority: authorityEncoded,
			Content:   man.Content,
			Encoded:   rawMan,
			Signature: sig,
		}

		if !*noopFlag {











			tkey, err := remote.PutTestimony(testimony)
			if err != nil {
				command.Errorf("failed to publish testimony (%q) to %q: %s", man.Path, remote, err)
				continue
			}




			command.V(1).Infof("%q testimony key: %s", man.Path, tkey)

		}
	}

	return nil
}