bridge.go

  1// Package core contains the target-agnostic code to define and run a bridge
  2package core
  3
  4import (
  5	"fmt"
  6	"reflect"
  7	"regexp"
  8	"sort"
  9	"strings"
 10	"time"
 11
 12	"github.com/pkg/errors"
 13
 14	"github.com/MichaelMure/git-bug/cache"
 15	"github.com/MichaelMure/git-bug/repository"
 16)
 17
 18var ErrImportNotSupported = errors.New("import is not supported")
 19var ErrExportNotSupported = errors.New("export is not supported")
 20
 21const (
 22	keyTarget             = "target"
 23	bridgeConfigKeyPrefix = "git-bug.bridge"
 24)
 25
 26var bridgeImpl map[string]reflect.Type
 27
 28// BridgeParams holds parameters to simplify the bridge configuration without
 29// having to make terminal prompts.
 30type BridgeParams struct {
 31	Owner   string
 32	Project string
 33	URL     string
 34	Token   string
 35}
 36
 37// Bridge is a wrapper around a BridgeImpl that will bind low-level
 38// implementation with utility code to provide high-level functions.
 39type Bridge struct {
 40	Name     string
 41	repo     *cache.RepoCache
 42	impl     BridgeImpl
 43	importer Importer
 44	exporter Exporter
 45	conf     Configuration
 46	initDone bool
 47}
 48
 49// Register will register a new BridgeImpl
 50func Register(impl BridgeImpl) {
 51	if bridgeImpl == nil {
 52		bridgeImpl = make(map[string]reflect.Type)
 53	}
 54	bridgeImpl[impl.Target()] = reflect.TypeOf(impl)
 55}
 56
 57// Targets return all known bridge implementation target
 58func Targets() []string {
 59	var result []string
 60
 61	for key := range bridgeImpl {
 62		result = append(result, key)
 63	}
 64
 65	sort.Strings(result)
 66
 67	return result
 68}
 69
 70// Instantiate a new Bridge for a repo, from the given target and name
 71func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, error) {
 72	implType, ok := bridgeImpl[target]
 73	if !ok {
 74		return nil, fmt.Errorf("unknown bridge target %v", target)
 75	}
 76
 77	impl := reflect.New(implType).Elem().Interface().(BridgeImpl)
 78
 79	bridge := &Bridge{
 80		Name: name,
 81		repo: repo,
 82		impl: impl,
 83	}
 84
 85	return bridge, nil
 86}
 87
 88// LoadBridge instantiate a new bridge from a repo configuration
 89func LoadBridge(repo *cache.RepoCache, name string) (*Bridge, error) {
 90	bridge := &Bridge{
 91		Name: name,
 92		repo: repo,
 93	}
 94
 95	conf, err := bridge.loadConfig()
 96	if err != nil {
 97		return nil, err
 98	}
 99	bridge.conf = conf
100
101	target := bridge.conf[keyTarget]
102	implType, ok := bridgeImpl[target]
103	if !ok {
104		return nil, fmt.Errorf("unknown bridge target %v", target)
105	}
106
107	bridge.impl = reflect.New(implType).Elem().Interface().(BridgeImpl)
108
109	err = bridge.impl.ValidateConfig(bridge.conf)
110	if err != nil {
111		return nil, errors.Wrap(err, "invalid configuration")
112	}
113
114	return bridge, nil
115}
116
117// Attempt to retrieve a default bridge for the given repo. If zero or multiple
118// bridge exist, it fails.
119func DefaultBridge(repo *cache.RepoCache) (*Bridge, error) {
120	bridges, err := ConfiguredBridges(repo)
121	if err != nil {
122		return nil, err
123	}
124
125	if len(bridges) == 0 {
126		return nil, fmt.Errorf("no configured bridge")
127	}
128
129	if len(bridges) > 1 {
130		return nil, fmt.Errorf("multiple bridge are configured, you need to select one explicitely")
131	}
132
133	return LoadBridge(repo, bridges[0])
134}
135
136// ConfiguredBridges return the list of bridge that are configured for the given
137// repo
138func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) {
139	configs, err := repo.ReadConfigs(bridgeConfigKeyPrefix + ".")
140	if err != nil {
141		return nil, errors.Wrap(err, "can't read configured bridges")
142	}
143
144	re, err := regexp.Compile(bridgeConfigKeyPrefix + `.([^.]+)`)
145	if err != nil {
146		panic(err)
147	}
148
149	set := make(map[string]interface{})
150
151	for key := range configs {
152		res := re.FindStringSubmatch(key)
153
154		if res == nil {
155			continue
156		}
157
158		set[res[1]] = nil
159	}
160
161	result := make([]string, len(set))
162
163	i := 0
164	for key := range set {
165		result[i] = key
166		i++
167	}
168
169	return result, nil
170}
171
172// Remove a configured bridge
173func RemoveBridge(repo repository.RepoCommon, name string) error {
174	re, err := regexp.Compile(`^[a-zA-Z0-9]+`)
175	if err != nil {
176		panic(err)
177	}
178
179	if !re.MatchString(name) {
180		return fmt.Errorf("bad bridge fullname: %s", name)
181	}
182
183	keyPrefix := fmt.Sprintf("git-bug.bridge.%s", name)
184	return repo.RmConfigs(keyPrefix)
185}
186
187// Configure run the target specific configuration process
188func (b *Bridge) Configure(params BridgeParams) error {
189	conf, err := b.impl.Configure(b.repo, params)
190	if err != nil {
191		return err
192	}
193
194	err = b.impl.ValidateConfig(conf)
195	if err != nil {
196		return fmt.Errorf("invalid configuration: %v", err)
197	}
198
199	b.conf = conf
200	return b.storeConfig(conf)
201}
202
203func (b *Bridge) storeConfig(conf Configuration) error {
204	for key, val := range conf {
205		storeKey := fmt.Sprintf("git-bug.bridge.%s.%s", b.Name, key)
206
207		err := b.repo.StoreConfig(storeKey, val)
208		if err != nil {
209			return errors.Wrap(err, "error while storing bridge configuration")
210		}
211	}
212
213	return nil
214}
215
216func (b *Bridge) ensureConfig() error {
217	if b.conf == nil {
218		conf, err := b.loadConfig()
219		if err != nil {
220			return err
221		}
222		b.conf = conf
223	}
224
225	return nil
226}
227
228func (b *Bridge) loadConfig() (Configuration, error) {
229	keyPrefix := fmt.Sprintf("git-bug.bridge.%s.", b.Name)
230
231	pairs, err := b.repo.ReadConfigs(keyPrefix)
232	if err != nil {
233		return nil, errors.Wrap(err, "error while reading bridge configuration")
234	}
235
236	result := make(Configuration, len(pairs))
237	for key, value := range pairs {
238		key := strings.TrimPrefix(key, keyPrefix)
239		fmt.Println(key, value)
240		result[key] = value
241	}
242
243	return result, nil
244}
245
246func (b *Bridge) getImporter() Importer {
247	if b.importer == nil {
248		b.importer = b.impl.NewImporter()
249	}
250
251	return b.importer
252}
253
254func (b *Bridge) getExporter() Exporter {
255	if b.exporter == nil {
256		b.exporter = b.impl.NewExporter()
257	}
258
259	return b.exporter
260}
261
262func (b *Bridge) ensureInit() error {
263	if b.initDone {
264		return nil
265	}
266
267	importer := b.getImporter()
268	if importer != nil {
269		err := importer.Init(b.conf)
270		if err != nil {
271			return err
272		}
273	}
274
275	exporter := b.getExporter()
276	if exporter != nil {
277		err := exporter.Init(b.conf)
278		if err != nil {
279			return err
280		}
281	}
282
283	b.initDone = true
284
285	return nil
286}
287
288func (b *Bridge) ImportAll(since time.Time) error {
289	importer := b.getImporter()
290	if importer == nil {
291		return ErrImportNotSupported
292	}
293
294	err := b.ensureConfig()
295	if err != nil {
296		return err
297	}
298
299	err = b.ensureInit()
300	if err != nil {
301		return err
302	}
303
304	return importer.ImportAll(b.repo, since)
305}
306
307func (b *Bridge) ExportAll(since time.Time) error {
308	exporter := b.getExporter()
309	if exporter == nil {
310		return ErrExportNotSupported
311	}
312
313	err := b.ensureConfig()
314	if err != nil {
315		return err
316	}
317
318	err = b.ensureInit()
319	if err != nil {
320		return err
321	}
322
323	return exporter.ExportAll(b.repo, since)
324}