Skip to content

Commit 8f89b89

Browse files
committed
MEDIUM: Add acme section support
1 parent b7a168e commit 8f89b89

23 files changed

Lines changed: 1865 additions & 1 deletion

config-parser/init.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type ConfiguredParsers struct {
4747
CrtStore *Parsers
4848
Traces *Parsers
4949
LogProfile *Parsers
50+
Acme *Parsers
5051
// spoe parsers
5152
SPOEAgent *Parsers
5253
SPOEGroup *Parsers
@@ -99,4 +100,5 @@ func (p *configParser) initParserMaps() {
99100
p.Parsers[CrtStore] = map[string]*Parsers{}
100101
p.Parsers[Traces] = map[string]*Parsers{}
101102
p.Parsers[LogProfile] = map[string]*Parsers{}
103+
p.Parsers[Acme] = map[string]*Parsers{}
102104
}

config-parser/parser.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const (
4949
CrtStore Section = "crt-store"
5050
Traces Section = "traces"
5151
LogProfile Section = "log-profile"
52+
Acme Section = "acme"
5253
// spoe sections
5354
SPOEAgent Section = "spoe-agent"
5455
SPOEGroup Section = "spoe-group"

config-parser/reader.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,17 @@ func (p *configParser) ProcessLine(line string, parts []string, comment string,
391391
if p.Options.Log {
392392
p.Options.Logger.Tracef("%slog-profile section %s active", p.Options.LogPrefix, data.Name)
393393
}
394+
case "acme":
395+
parserSectionName := parser.(*extra.Section) //nolint:forcetypeassert
396+
rawData, _ := parserSectionName.Get(false)
397+
data := rawData.(*types.Section) //nolint:forcetypeassert
398+
config.Acme = p.getAcmeParser()
399+
config.Acme.Section = *data
400+
p.Parsers[Acme][data.Name] = config.Acme
401+
config.Active = config.Acme
402+
if p.Options.Log {
403+
p.Options.Logger.Tracef("%sacme section %s active", p.Options.LogPrefix, data.Name)
404+
}
394405
case "snippet_beg":
395406
config.Previous = config.Active
396407
config.Active = &Parsers{

config-parser/section-parsers.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func (p *configParser) createParsers(parser map[string]ParserInterface, sequence
5252
addParser(parser, &sequence, &extra.Section{Name: "crt-store"})
5353
addParser(parser, &sequence, &extra.Section{Name: "traces"})
5454
addParser(parser, &sequence, &extra.Section{Name: "log-profile"})
55+
addParser(parser, &sequence, &extra.Section{Name: "acme"})
5556
if !p.Options.DisableUnProcessed {
5657
addParser(parser, &sequence, &extra.UnProcessed{})
5758
}
@@ -1004,3 +1005,17 @@ func (p *configParser) getLogProfileParser() *Parsers {
10041005
addParser(parser, &sequence, &parsers.OnLogStep{})
10051006
return p.createParsers(parser, sequence)
10061007
}
1008+
1009+
func (p *configParser) getAcmeParser() *Parsers {
1010+
parser := map[string]ParserInterface{}
1011+
sequence := []Section{}
1012+
addParser(parser, &sequence, &simple.Word{Name: "account-key"})
1013+
addParser(parser, &sequence, &simple.Number{Name: "bits"})
1014+
addParser(parser, &sequence, &simple.Word{Name: "challenge"})
1015+
addParser(parser, &sequence, &simple.Word{Name: "contact"})
1016+
addParser(parser, &sequence, &simple.Word{Name: "curves"})
1017+
addParser(parser, &sequence, &simple.Word{Name: "directory"})
1018+
addParser(parser, &sequence, &simple.Word{Name: "keytype"})
1019+
addParser(parser, &sequence, &simple.Word{Name: "map"})
1020+
return p.createParsers(parser, sequence)
1021+
}

config-parser/writer.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,26 @@ func (p *configParser) String() string {
4141
p.writeParsers("", p.Parsers[Comments][CommentsSectionName], &result, false)
4242
p.writeParsers("global", p.Parsers[Global][GlobalSectionName], &result, true)
4343

44-
sections := []Section{Defaults, UserList, Peers, Mailers, Resolvers, Cache, Ring, Traces, LogForward, LogProfile, HTTPErrors, CrtStore, Frontends, Backends, Listen, Program, FCGIApp}
44+
sections := []Section{
45+
Defaults,
46+
UserList,
47+
Peers,
48+
Mailers,
49+
Resolvers,
50+
Cache,
51+
Ring,
52+
Traces,
53+
LogForward,
54+
LogProfile,
55+
HTTPErrors,
56+
Acme,
57+
CrtStore,
58+
Frontends,
59+
Backends,
60+
Listen,
61+
Program,
62+
FCGIApp,
63+
}
4564

4665
for _, section := range sections {
4766
var sortedSections []string

configuration/acme_provider.go

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
// Copyright 2025 HAProxy Technologies
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+
//
15+
16+
package configuration
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
22+
strfmt "github.com/go-openapi/strfmt"
23+
parser "github.com/haproxytech/client-native/v6/config-parser"
24+
parser_errors "github.com/haproxytech/client-native/v6/config-parser/errors"
25+
"github.com/haproxytech/client-native/v6/config-parser/types"
26+
"github.com/haproxytech/client-native/v6/misc"
27+
"github.com/haproxytech/client-native/v6/models"
28+
)
29+
30+
type Acme interface {
31+
GetAcmeProviders(transactionID string) (int64, models.AcmeProviders, error)
32+
GetAcmeProvider(name, transactionID string) (int64, *models.AcmeProvider, error)
33+
CreateAcmeProvider(data *models.AcmeProvider, transactionID string, version int64) error
34+
EditAcmeProvider(name string, data *models.AcmeProvider, transactionID string, version int64) error
35+
DeleteAcmeProvider(name, transactionID string, version int64) error
36+
}
37+
38+
func (c *client) GetAcmeProviders(transactionID string) (int64, models.AcmeProviders, error) {
39+
p, err := c.GetParser(transactionID)
40+
if err != nil {
41+
return 0, nil, err
42+
}
43+
44+
v, err := c.GetVersion(transactionID)
45+
if err != nil {
46+
return 0, nil, err
47+
}
48+
49+
names, err := p.SectionsGet(parser.Acme)
50+
if err != nil {
51+
return v, nil, err
52+
}
53+
54+
acmes := make(models.AcmeProviders, 0, len(names))
55+
56+
for _, name := range names {
57+
a, err := ParseAcmeProvider(p, name)
58+
if err == nil {
59+
acmes = append(acmes, a)
60+
}
61+
}
62+
63+
return v, acmes, nil
64+
}
65+
66+
func (c *client) GetAcmeProvider(name, transactionID string) (int64, *models.AcmeProvider, error) {
67+
p, err := c.GetParser(transactionID)
68+
if err != nil {
69+
return 0, nil, err
70+
}
71+
72+
v, err := c.GetVersion(transactionID)
73+
if err != nil {
74+
return 0, nil, err
75+
}
76+
77+
if !p.SectionExists(parser.Acme, name) {
78+
return v, nil, NewConfError(ErrObjectDoesNotExist,
79+
fmt.Sprintf("%s section '%s' does not exist", AcmeParentName, name))
80+
}
81+
82+
acme, err := ParseAcmeProvider(p, name)
83+
if err != nil {
84+
return 0, nil, err
85+
}
86+
87+
return v, acme, nil
88+
}
89+
90+
func (c *client) DeleteAcmeProvider(name, transactionID string, version int64) error {
91+
return c.deleteSection(parser.Acme, name, transactionID, version)
92+
}
93+
94+
func (c *client) CreateAcmeProvider(data *models.AcmeProvider, transactionID string, version int64) error {
95+
if c.UseModelsValidation {
96+
validationErr := data.Validate(strfmt.Default)
97+
if validationErr != nil {
98+
return NewConfError(ErrValidationError, validationErr.Error())
99+
}
100+
}
101+
102+
p, t, err := c.loadDataForChange(transactionID, version)
103+
if err != nil {
104+
return c.HandleError(data.Name, "", "", t, transactionID == "", err)
105+
}
106+
107+
if p.SectionExists(parser.Acme, data.Name) {
108+
e := NewConfError(ErrObjectAlreadyExists, fmt.Sprintf("%s %s already exists", parser.Acme, data.Name))
109+
return c.HandleError(data.Name, "", "", t, transactionID == "", e)
110+
}
111+
112+
if err = p.SectionsCreate(parser.Acme, data.Name); err != nil {
113+
return c.HandleError(data.Name, "", "", t, transactionID == "", err)
114+
}
115+
116+
if err = SerializeAcmeProvider(p, data); err != nil {
117+
return c.HandleError(data.Name, "", "", t, transactionID == "", err)
118+
}
119+
120+
return c.SaveData(p, t, transactionID == "")
121+
}
122+
123+
func (c *client) EditAcmeProvider(name string, data *models.AcmeProvider, transactionID string, version int64) error {
124+
if c.UseModelsValidation {
125+
validationErr := data.Validate(strfmt.Default)
126+
if validationErr != nil {
127+
return NewConfError(ErrValidationError, validationErr.Error())
128+
}
129+
}
130+
131+
if data.Name == "" {
132+
data.Name = name
133+
}
134+
135+
p, t, err := c.loadDataForChange(transactionID, version)
136+
if err != nil {
137+
return err
138+
}
139+
140+
if !p.SectionExists(parser.Acme, data.Name) {
141+
e := NewConfError(ErrObjectAlreadyExists, fmt.Sprintf("%s %s does not exists", parser.Acme, data.Name))
142+
return c.HandleError(data.Name, "", "", t, transactionID == "", e)
143+
}
144+
145+
if err = SerializeAcmeProvider(p, data); err != nil {
146+
return err
147+
}
148+
149+
return c.SaveData(p, t, transactionID == "")
150+
}
151+
152+
func ParseAcmeProvider(p parser.Parser, name string) (*models.AcmeProvider, error) {
153+
acme := &models.AcmeProvider{Name: name}
154+
155+
if data, err := p.SectionGet(parser.Acme, name); err == nil {
156+
d, ok := data.(types.Section)
157+
if ok {
158+
acme.Metadata = parseMetadata(d.Comment)
159+
}
160+
}
161+
162+
stringAttr := map[string]*string{
163+
"account-key": &acme.AccountKey,
164+
"challenge": &acme.Challenge,
165+
"contact": &acme.Contact,
166+
"curves": &acme.Curves,
167+
"directory": &acme.Directory,
168+
"keytype": &acme.Keytype,
169+
"map": &acme.Map,
170+
}
171+
172+
for kw, dest := range stringAttr {
173+
val, err := p.Get(parser.Acme, name, kw)
174+
if err != nil {
175+
if errors.Is(err, parser_errors.ErrFetch) {
176+
continue
177+
}
178+
return nil, err
179+
}
180+
str, ok := val.(*types.StringC)
181+
if !ok {
182+
return nil, misc.CreateTypeAssertError(kw)
183+
}
184+
*dest = str.Value
185+
}
186+
187+
// bits
188+
val, err := p.Get(parser.Acme, name, "bits")
189+
if err != nil {
190+
if !errors.Is(err, parser_errors.ErrFetch) {
191+
return nil, err
192+
}
193+
} else {
194+
ic, ok := val.(*types.Int64C)
195+
if !ok {
196+
return nil, misc.CreateTypeAssertError("bits")
197+
}
198+
acme.Bits = misc.Ptr(ic.Value)
199+
}
200+
201+
return acme, nil
202+
}
203+
204+
func SerializeAcmeProvider(p parser.Parser, acme *models.AcmeProvider) error {
205+
if acme == nil {
206+
return fmt.Errorf("empty %s section", AcmeParentName)
207+
}
208+
209+
if acme.Metadata != nil {
210+
comment, err := serializeMetadata(acme.Metadata)
211+
if err != nil {
212+
return err
213+
}
214+
if err := p.SectionCommentSet(parser.Acme, acme.Name, comment); err != nil {
215+
return err
216+
}
217+
}
218+
219+
stringAttr := map[string]string{
220+
"account-key": acme.AccountKey,
221+
"challenge": acme.Challenge,
222+
"contact": acme.Contact,
223+
"curves": acme.Curves,
224+
"directory": acme.Directory,
225+
"keytype": acme.Keytype,
226+
"map": acme.Map,
227+
}
228+
229+
for kw, val := range stringAttr {
230+
if val != "" {
231+
if err := p.Set(parser.Acme, acme.Name, kw, types.StringC{Value: val}); err != nil {
232+
return err
233+
}
234+
} else {
235+
_ = p.Delete(parser.Acme, acme.Name, kw)
236+
}
237+
}
238+
239+
if acme.Bits != nil && *acme.Bits != 0 {
240+
if err := p.Set(parser.Acme, acme.Name, "bits", types.Int64C{Value: *acme.Bits}); err != nil {
241+
return err
242+
}
243+
} else {
244+
_ = p.Delete(parser.Acme, acme.Name, "bits")
245+
}
246+
247+
return nil
248+
}

configuration/configuration.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const (
5050
CrtStoreParentName = "crt-store"
5151
TracesParentName = "traces"
5252
LogProfileParentName = "log-profile"
53+
AcmeParentName = "acme"
5354
)
5455

5556
// ClientParams is just a placeholder for all client options

configuration/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
type Configuration interface {
3434
Parser
3535
ACL
36+
Acme
3637
Backend
3738
Bind
3839
Cache

configuration/structured.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Structured interface {
3434
StructuredFCGIApp
3535
StructuredMailersSection
3636
StructuredTraces
37+
StructuredAcmeProvider
3738
}
3839

3940
type StructuredToParserArgs struct {

0 commit comments

Comments
 (0)