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