Skip to content

Commit a3928d8

Browse files
authored
feat: add api of config migrate, export and import (#1893)
1 parent bc0de43 commit a3928d8

File tree

12 files changed

+769
-7
lines changed

12 files changed

+769
-7
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package migrate
19+
20+
import (
21+
"context"
22+
23+
"github.com/apisix/manager-api/internal/core/store"
24+
"github.com/apisix/manager-api/internal/log"
25+
)
26+
27+
func isConflicted(ctx context.Context, new *DataSet) (bool, *DataSet) {
28+
isConflict := false
29+
conflictedData := newDataSet()
30+
store.RangeStore(func(key store.HubKey, s *store.GenericStore) bool {
31+
new.rangeData(key, func(i int, obj interface{}) bool {
32+
// Only check key of store conflict for now.
33+
// TODO: Maybe check name of some entiries.
34+
_, err := s.CreateCheck(obj)
35+
if err != nil {
36+
isConflict = true
37+
err = conflictedData.Add(obj)
38+
if err != nil {
39+
log.Errorf("Add obj to conflict list failed:%s", err)
40+
return true
41+
}
42+
}
43+
return true
44+
})
45+
return true
46+
})
47+
return isConflict, conflictedData
48+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package migrate
19+
20+
import (
21+
"errors"
22+
23+
"github.com/apisix/manager-api/internal/core/entity"
24+
"github.com/apisix/manager-api/internal/core/store"
25+
)
26+
27+
type DataSet struct {
28+
Counsumers []*entity.Consumer
29+
Routes []*entity.Route
30+
Services []*entity.Service
31+
SSLs []*entity.SSL
32+
Upstreams []*entity.Upstream
33+
Scripts []*entity.Script
34+
GlobalPlugins []*entity.GlobalPlugins
35+
PluginConfigs []*entity.PluginConfig
36+
}
37+
38+
func newDataSet() *DataSet {
39+
return &DataSet{
40+
Counsumers: make([]*entity.Consumer, 0),
41+
Routes: make([]*entity.Route, 0),
42+
Services: make([]*entity.Service, 0),
43+
SSLs: make([]*entity.SSL, 0),
44+
Upstreams: make([]*entity.Upstream, 0),
45+
Scripts: make([]*entity.Script, 0),
46+
GlobalPlugins: make([]*entity.GlobalPlugins, 0),
47+
PluginConfigs: make([]*entity.PluginConfig, 0),
48+
}
49+
}
50+
51+
func (a *DataSet) rangeData(key store.HubKey, f func(int, interface{}) bool) {
52+
switch key {
53+
case store.HubKeyConsumer:
54+
for i, v := range a.Counsumers {
55+
if !f(i, v) {
56+
break
57+
}
58+
}
59+
case store.HubKeyRoute:
60+
for i, v := range a.Routes {
61+
if !f(i, v) {
62+
break
63+
}
64+
}
65+
case store.HubKeyService:
66+
for i, v := range a.Services {
67+
if !f(i, v) {
68+
break
69+
}
70+
}
71+
case store.HubKeySsl:
72+
for i, v := range a.SSLs {
73+
if !f(i, v) {
74+
break
75+
}
76+
}
77+
case store.HubKeyUpstream:
78+
for i, v := range a.Upstreams {
79+
if !f(i, v) {
80+
break
81+
}
82+
}
83+
case store.HubKeyScript:
84+
for i, v := range a.Scripts {
85+
if !f(i, v) {
86+
break
87+
}
88+
}
89+
case store.HubKeyGlobalRule:
90+
for i, v := range a.GlobalPlugins {
91+
if !f(i, v) {
92+
break
93+
}
94+
}
95+
case store.HubKeyPluginConfig:
96+
for i, v := range a.PluginConfigs {
97+
if !f(i, v) {
98+
break
99+
}
100+
}
101+
}
102+
}
103+
104+
func (a *DataSet) Add(obj interface{}) error {
105+
var err error = nil
106+
switch obj := obj.(type) {
107+
case *entity.Consumer:
108+
a.Counsumers = append(a.Counsumers, obj)
109+
case *entity.Route:
110+
a.Routes = append(a.Routes, obj)
111+
case *entity.Service:
112+
a.Services = append(a.Services, obj)
113+
case *entity.SSL:
114+
a.SSLs = append(a.SSLs, obj)
115+
case *entity.Upstream:
116+
a.Upstreams = append(a.Upstreams, obj)
117+
case *entity.Script:
118+
a.Scripts = append(a.Scripts, obj)
119+
case *entity.GlobalPlugins:
120+
a.GlobalPlugins = append(a.GlobalPlugins, obj)
121+
case *entity.PluginConfig:
122+
a.PluginConfigs = append(a.PluginConfigs, obj)
123+
default:
124+
err = errors.New("Unknown type of obj")
125+
}
126+
return err
127+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package migrate
19+
20+
import (
21+
"context"
22+
"encoding/json"
23+
"errors"
24+
25+
"github.com/apisix/manager-api/internal/core/store"
26+
"github.com/apisix/manager-api/internal/log"
27+
)
28+
29+
var (
30+
ErrConflict = errors.New("conflict")
31+
)
32+
33+
func Export(ctx context.Context) ([]byte, error) {
34+
exportData := newDataSet()
35+
store.RangeStore(func(key store.HubKey, s *store.GenericStore) bool {
36+
s.Range(ctx, func(_ string, obj interface{}) bool {
37+
err := exportData.Add(obj)
38+
if err != nil {
39+
log.Errorf("Add obj to export list failed:%s", err)
40+
return true
41+
}
42+
return true
43+
})
44+
return true
45+
})
46+
47+
data, err := json.Marshal(exportData)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
return data, nil
53+
}
54+
55+
type ConflictMode int
56+
57+
const (
58+
ModeReturn ConflictMode = iota
59+
ModeOverwrite
60+
ModeSkip
61+
)
62+
63+
func Import(ctx context.Context, data []byte, mode ConflictMode) (*DataSet, error) {
64+
importData := newDataSet()
65+
err := json.Unmarshal(data, &importData)
66+
if err != nil {
67+
return nil, err
68+
}
69+
conflict, conflictData := isConflicted(ctx, importData)
70+
if conflict && mode == ModeReturn {
71+
return conflictData, ErrConflict
72+
}
73+
store.RangeStore(func(key store.HubKey, s *store.GenericStore) bool {
74+
importData.rangeData(key, func(i int, obj interface{}) bool {
75+
_, e := s.CreateCheck(obj)
76+
if e != nil {
77+
switch mode {
78+
case ModeSkip:
79+
return true
80+
case ModeOverwrite:
81+
_, e := s.Update(ctx, obj, true)
82+
if e != nil {
83+
err = e
84+
return false
85+
}
86+
}
87+
} else {
88+
_, e := s.Create(ctx, obj)
89+
if err != nil {
90+
err = e
91+
return false
92+
}
93+
}
94+
return true
95+
})
96+
return true
97+
})
98+
return nil, err
99+
}

api/internal/core/store/store.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,12 @@ func (s *GenericStore) List(_ context.Context, input ListInput) (*ListOutput, er
232232
return output, nil
233233
}
234234

235+
func (s *GenericStore) Range(_ context.Context, f func(key string, obj interface{}) bool) {
236+
s.cache.Range(func(key, value interface{}) bool {
237+
return f(key.(string), value)
238+
})
239+
}
240+
235241
func (s *GenericStore) ingestValidate(obj interface{}) (err error) {
236242
if s.opt.Validator != nil {
237243
if err := s.opt.Validator.Validate(obj); err != nil {

api/internal/core/store/storehub.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ func GetStore(key HubKey) *GenericStore {
8282
panic(fmt.Sprintf("no store with key: %s", key))
8383
}
8484

85+
func RangeStore(f func(key HubKey, store *GenericStore) bool) {
86+
for k, s := range storeHub {
87+
if k != "" && s != nil {
88+
if !f(k, s) {
89+
break
90+
}
91+
}
92+
}
93+
}
94+
8595
func InitStores() error {
8696
err := InitStore(HubKeyConsumer, GenericStoreOption{
8797
BasePath: conf.ETCDConfig.Prefix + "/consumers",

0 commit comments

Comments
 (0)