• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / useradm / 1807193300

08 May 2025 10:54AM UTC coverage: 59.747% (-27.3%) from 87.019%
1807193300

Pull #439

gitlab-ci

alfrunes
Merge `alfrunes:1.22.x` into `mendersoftware:1.22.x`
Pull Request #439: :building_construction: Upgrade dependencies

2363 of 3955 relevant lines covered (59.75%)

12.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/model/plans.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package model
15

16
import (
17
        "encoding/json"
18
        "fmt"
19
        "io"
20
        "os"
21
        "path"
22
        "regexp"
23
        "sort"
24

25
        "github.com/pkg/errors"
26
        "gopkg.in/yaml.v3"
27
)
28

29
var (
30
        validPlanName  = regexp.MustCompile(`^[a-zA-Z0-9_\\-]*$`)
31
        PlanList       = []Plan{}
32
        PlanNameToPlan = map[string]*Plan{}
33
)
34

35
type Features struct {
36
        // RBAC
37
        RBAC bool `json:"rbac" bson:"rbac"`
38

39
        // audit logs
40
        AuditLogs bool `json:"audit_logs" bson:"audit_logs"`
41

42
        // dynamic groups
43
        DynamicGroups bool `json:"dynamic_groups" bson:"dynamic_groups"`
44

45
        // remote terminal
46
        Terminal bool `json:"terminal" bson:"terminal"`
47

48
        // file transfer
49
        FileTransfer bool `json:"file_transfer" bson:"file_transfer"`
50

51
        // configuration
52
        Configuration bool `json:"configuration" bson:"configuration"`
53

54
        // monitoring
55
        Monitoring bool `json:"monitoring" bson:"monitoring"`
56

57
        // reporting
58
        Reporting bool `json:"reporting" bson:"reporting"`
59
}
60

61
type PlanLimits struct {
62
        // maximum number of devices
63
        Devices int `json:"devices" bson:"devices"`
64

65
        // maximum number of users
66
        Users int `json:"users" bson:"users"`
67

68
        // audit logs history in days
69
        AuditLogsDays int `json:"audit_logs_days" bson:"audit_logs_days"`
70
}
71

72
type Plan struct {
73
        // unique name used as identifier
74
        Name string `json:"name" bson:"_id"`
75

76
        // name to display
77
        DisplayName string `json:"display_name" bson:"display_name"`
78

79
        // feature set
80
        Features *Features `json:"features" bson:"features"`
81
}
82

83
type PlanBindingDetails struct {
84
        // tenant can only have single plan binding
85
        TenantID string `json:"-" bson:"_id"`
86

87
        // plan name
88
        Plan Plan `json:"plan" bson:"plan"`
89

90
        // limits
91
        Limits *PlanLimits `json:"limits,omitempty" bson:"limits,omitempty"`
92
}
93

94
type PlanBinding struct {
95
        // tenant can only have single plan binding
96
        TenantID string `json:"-" bson:"_id"`
97

98
        // plan name
99
        PlanName string `json:"plan_name" bson:"plan_name"`
100

101
        // limits
102
        Limits *PlanLimits `json:"limits,omitempty" bson:"limits,omitempty"`
103
}
104

105
func ValidatePlanName(name string) error {
×
106
        //plan of no name is not accepted
×
107
        if !(len(name) > 0) {
×
108
                return errors.New("plan name cannot be empty")
×
109
        }
×
110

111
        //plan name must be composed of letters or numbers with possible _ or -
112
        if !validPlanName.MatchString(name) {
×
113
                return errors.New("invalid plan name")
×
114
        }
×
115
        return nil
×
116
}
117

118
func (p *Plan) Validate() error {
×
119
        //plan of not valid name is not accepted
×
120
        if err := ValidatePlanName(p.Name); err != nil {
×
121
                return err
×
122
        }
×
123

124
        return nil
×
125
}
126

127
func jsonSerializerDeserializer(serialize interface{}, deserialized interface{}) error {
×
128
        b, err := json.Marshal(serialize)
×
129
        if err != nil {
×
130
                return err
×
131
        }
×
132
        err = json.Unmarshal(b, deserialized)
×
133
        return err
×
134
}
135

136
// LoadPlans loads Plan definitions from filePath.
137
// This function also checks for the validity of the definitions.
138
func LoadPlans(filepath string) error {
×
139
        var res struct {
×
140
                Plans map[string]interface{} `json:"plans" yaml:"plans"`
×
141
        }
×
142
        var decoder func(r io.Reader, v interface{}) error
×
143
        switch path.Ext(filepath) {
×
144
        case ".json":
×
145
                decoder = func(r io.Reader, v interface{}) error {
×
146
                        return json.NewDecoder(r).
×
147
                                Decode(v)
×
148
                }
×
149
        case ".yaml", ".yml":
×
150
                decoder = func(r io.Reader, v interface{}) error {
×
151
                        return yaml.NewDecoder(r).
×
152
                                Decode(v)
×
153
                }
×
154
        default:
×
155
                return fmt.Errorf(
×
156
                        "file extension for %q not recognized",
×
157
                        path.Base(filepath),
×
158
                )
×
159
        }
160
        f, err := os.Open(filepath)
×
161
        if err != nil {
×
162
                return err
×
163
        }
×
164
        defer f.Close()
×
165
        err = decoder(f, &res)
×
166
        if err != nil {
×
167
                return err
×
168
        }
×
169
        return loadPlans(res.Plans)
×
170
}
171

172
func loadPlans(
173
        rawPlans map[string]interface{},
174
) error {
×
175
        var (
×
176
                err error
×
177

×
178
                newPlanList []Plan
×
179
                newPlans    map[string]*Plan
×
180
        )
×
181

×
182
        if rawPlans != nil {
×
183
                if err := jsonSerializerDeserializer(rawPlans, &newPlans); err != nil {
×
184
                        return fmt.Errorf(
×
185
                                "failed to parse plans: %w",
×
186
                                err,
×
187
                        )
×
188
                }
×
189
                newPlanList, err = buildPlanList(newPlans)
×
190
                if err != nil {
×
191
                        return err
×
192
                }
×
193
        }
194

195
        PlanList = newPlanList
×
196
        PlanNameToPlan = newPlans
×
197
        return nil
×
198
}
199

200
func buildPlanList(newPlans map[string]*Plan) ([]Plan, error) {
×
201
        newPlanList := make([]Plan, 0, len(newPlans))
×
202
        for name, plan := range newPlans {
×
203
                if plan == nil {
×
204
                        return nil, fmt.Errorf("plan %q is empty", name)
×
205
                }
×
206
                plan.Name = name
×
207

×
208
                err := plan.Validate()
×
209
                if err != nil {
×
210
                        return nil, fmt.Errorf(
×
211
                                "invalid plan definition: %w",
×
212
                                err,
×
213
                        )
×
214
                }
×
215
                newPlanList = append(newPlanList, *plan)
×
216
        }
217
        sort.Slice(newPlanList, func(i, j int) bool {
×
218
                return newPlanList[i].Name < newPlanList[j].Name
×
219
        })
×
220
        return newPlanList, nil
×
221
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc