Skip to content

Commit

Permalink
egctl create httpproxy cmd support update or create autocertmanager (#…
Browse files Browse the repository at this point in the history
…1188)

* egctl create httpproxy cmd support update or create autocertmanager

* update doc

* add test
  • Loading branch information
suchen-sci committed Jan 5, 2024
1 parent 0a4f3c3 commit b5f08e1
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 11 deletions.
127 changes: 122 additions & 5 deletions cmd/client/commandv2/create/createhttpproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package create
import (
"encoding/base64"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/megaease/easegress/v2/pkg/filters"
"github.com/megaease/easegress/v2/pkg/filters/proxies"
"github.com/megaease/easegress/v2/pkg/filters/proxies/httpproxy"
"github.com/megaease/easegress/v2/pkg/object/autocertmanager"
"github.com/megaease/easegress/v2/pkg/object/httpserver/routers"
"github.com/megaease/easegress/v2/pkg/util/codectool"
"github.com/spf13/cobra"
Expand All @@ -47,10 +49,15 @@ type HTTPProxyOptions struct {
CertFiles []string
KeyFiles []string

caCert string
certs []string
keys []string
rules []*HTTPProxyRule
AutoCertDomainName string
AutoCertEmail string
AutoCertDNSProvider []string

caCert string
certs []string
keys []string
rules []*HTTPProxyRule
dnsProvider map[string]string
}

var httpProxyOptions = &HTTPProxyOptions{}
Expand All @@ -63,7 +70,11 @@ egctl create httpproxy NAME --port PORT \
[--auto-cert] \
[--ca-cert-file CA_CERT_FILE] \
[--cert-file CERT_FILE] \
[--key-file KEY_FILE]
[--key-file KEY_FILE] \
[--auto-cert-email EMAIL] \
[--auto-cert-domain-name DOMAIN_NAME] \
[--dns-provider KEY=VALUE] \
[--dns-provider KEY2=VALUE2]
# Create a HTTPServer (with port 10080) and corresponding Pipelines to direct
# request with path "/bar" to "http:https://127.0.0.1:8080" and "http:https://127.0.0.1:8081" and
Expand All @@ -76,6 +87,27 @@ egctl create httpproxy demo --port 10080 \
# with path prefix "foo.com/prefix" to "http:https://127.0.0.1:8083".
egctl create httpproxy demo2 --port 10081 \
--rule="foo.com/prefix*=http:https://127.0.0.1:8083"
# Create HTTPServer, Pipelines with a new AutoCertManager.
# auto-cert-email is required for creating a new AutoCertManager.
# If an AutoCertManager exists, this updates its email field.
egctl create httpproxy demo2 --port 10081 \
--rule="/bar=http:https://127.0.0.1:8083" \
--auto-cert \
--auto-cert-email [email protected] \
--auto-cert-domain-name="*.foo.com" \
--dns-provider name=dnspod \
--dns-provider zone=megaease.com \
--dns-provider="apiToken=<tokenvalue>"
# Create HTTPServer, Pipelines with an existing AutoCertManager and update it.
egctl create httpproxy demo2 --port 10081 \
--rule="/bar=http:https://127.0.0.1:8083" \
--auto-cert \
--auto-cert-domain-name="*.foo.com" \
--dns-provider name=dnspod \
--dns-provider zone=megaease.com \
--dns-provider="apiToken=<tokenvalue>"
`

// HTTPProxyCmd returns create command of HTTPProxy.
Expand Down Expand Up @@ -103,6 +135,9 @@ func HTTPProxyCmd() *cobra.Command {
cmd.Flags().StringVar(&o.CaCertFile, "ca-cert-file", "", "CA cert file")
cmd.Flags().StringArrayVar(&o.CertFiles, "cert-file", []string{}, "Cert file")
cmd.Flags().StringArrayVar(&o.KeyFiles, "key-file", []string{}, "Key file")
cmd.Flags().StringVar(&o.AutoCertDomainName, "auto-cert-domain-name", "", "Auto cert domain name")
cmd.Flags().StringArrayVar(&o.AutoCertDNSProvider, "dns-provider", []string{}, "Auto cert DNS provider")
cmd.Flags().StringVar(&o.AutoCertEmail, "auto-cert-email", "", "Auto cert email")
return cmd
}

Expand Down Expand Up @@ -134,6 +169,20 @@ func httpProxyRun(cmd *cobra.Command, args []string) error {
for _, p := range pls {
allSpec = append(allSpec, p)
}
if o.AutoCertDomainName != "" {
autoCertSpec, err := o.TranslateAutoCertManager()
if err != nil {
return err
}
generalSpec, err := toGeneralSpec(autoCertSpec)
if err != nil {
return err
}
err = resources.ApplyObject(cmd, generalSpec)
if err != nil {
return err
}
}
for _, s := range allSpec {
spec, err := toGeneralSpec(s)
if err != nil {
Expand Down Expand Up @@ -194,6 +243,27 @@ func (o *HTTPProxyOptions) Parse() error {
keys = append(keys, key)
}
o.keys = keys

// parse dns provider
if o.AutoCertDomainName != "" || len(o.AutoCertDNSProvider) != 0 {
if !o.AutoCert {
return fmt.Errorf("auto cert domain name or dns provider is set, but auto cert is not enabled")
}
if o.AutoCertDomainName == "" {
return fmt.Errorf("auto cert domain name is required when provide dns provider")
}
if len(o.AutoCertDNSProvider) == 0 {
return fmt.Errorf("auto cert dns provider is required when provide auto cert domain name")
}
}
o.dnsProvider = map[string]string{}
for _, dnsProvider := range o.AutoCertDNSProvider {
parts := strings.SplitN(dnsProvider, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("dns provider %s should in format 'name=secret', invalid format", dnsProvider)
}
o.dnsProvider[parts[0]] = parts[1]
}
return nil
}

Expand Down Expand Up @@ -258,6 +328,53 @@ func (o *HTTPProxyOptions) translateRules() (routers.Rules, []*specs.PipelineSpe
return rules, pipelines
}

var handleReqHook = general.HandleRequest

// TranslateAutoCertManager translates AutoCertManagerSpec.
func (o *HTTPProxyOptions) TranslateAutoCertManager() (*specs.AutoCertManagerSpec, error) {
url := general.MakePath(general.ObjectsURL)
body, err := handleReqHook(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
allSpecs, err := general.UnmarshalMapInterface(body, true)
if err != nil {
return nil, err
}
var spec *specs.AutoCertManagerSpec
for _, s := range allSpecs {
if s["kind"] == "AutoCertManager" {
if spec == nil {
spec = &specs.AutoCertManagerSpec{}
data, err := codectool.MarshalYAML(s)
if err != nil {
return nil, err
}
if err := codectool.Unmarshal(data, spec); err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("there are more than one AutoCertManager")
}
}
}
if spec == nil {
if o.AutoCertEmail != "" {
spec = specs.NewAutoCertManagerSpec()
spec.Email = o.AutoCertEmail
} else {
return nil, fmt.Errorf("there is no AutoCertManager and auto-cert-email is not set, please create one or set auto-cert-email")
}
} else if o.AutoCertEmail != "" {
spec.Email = o.AutoCertEmail
}
spec.AddOrUpdateDomain(&autocertmanager.DomainSpec{
Name: o.AutoCertDomainName,
DNSProvider: o.dnsProvider,
})
return spec, nil
}

func toGeneralSpec(data interface{}) (*general.Spec, error) {
var yamlStr []byte
var err error
Expand Down
102 changes: 97 additions & 5 deletions cmd/client/commandv2/create/createhttpproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,28 @@ func TestCreateHTTPProxyOptions(t *testing.T) {
"foo.com/bar*=http:https://127.0.0.1:9095",
"/bar=http:https://127.0.0.1:9095",
},
TLS: true,
AutoCert: true,
CaCertFile: createCert("ca.cert"),
CertFiles: []string{createCert("cert1"), createCert("cert2")},
KeyFiles: []string{createCert("key1"), createCert("key2")},
TLS: true,
AutoCert: true,
CaCertFile: createCert("ca.cert"),
CertFiles: []string{createCert("cert1"), createCert("cert2")},
KeyFiles: []string{createCert("key1"), createCert("key2")},
AutoCertEmail: "[email protected]",
AutoCertDomainName: "*.easegress.example",
AutoCertDNSProvider: []string{
"name=dnspod",
"zone=easegress.com",
"apiToken=abc",
},
}
o.Complete([]string{"test"})
err = o.Parse()
assert.Nil(err)

// auto cert
assert.Equal("dnspod", o.dnsProvider["name"])
assert.Equal("easegress.com", o.dnsProvider["zone"])
assert.Equal("abc", o.dnsProvider["apiToken"])

hs, pls := o.Translate()

// meta
Expand Down Expand Up @@ -383,3 +395,83 @@ func TestCreateHTTPProxyCmd(t *testing.T) {
err = httpProxyRun(cmd, []string{"demo"})
assert.NotNil(t, err)
}

func TestTranslateAutoCertManager(t *testing.T) {
assert := assert.New(t)

originalHook := handleReqHook
defer func() {
handleReqHook = originalHook
}()
handleReqHook = func(httpMethod string, path string, yamlBody []byte) ([]byte, error) {
return []byte("[]"), nil
}
option := &HTTPProxyOptions{
AutoCert: true,
AutoCertEmail: "[email protected]",
AutoCertDomainName: "*.easegress.example",
AutoCertDNSProvider: []string{
"name=dnspod",
"zone=easegress.com",
"apiToken=abc",
},
}
option.Complete([]string{"test"})
err := option.Parse()
assert.Nil(err)

spec, err := option.TranslateAutoCertManager()
assert.Nil(err)
assert.Equal("AutoCertManager", spec.Kind)
assert.Equal("autocertmanager", spec.Name)
assert.Equal("[email protected]", spec.Email)
assert.Equal(1, len(spec.Domains))
assert.Equal("*.easegress.example", spec.Domains[0].Name)
assert.Equal(map[string]string{
"name": "dnspod",
"zone": "easegress.com",
"apiToken": "abc",
}, spec.Domains[0].DNSProvider)

handleReqHook = func(httpMethod string, path string, yamlBody []byte) ([]byte, error) {
return []byte(`[
{
"kind": "AutoCertManager",
"name": "autocert",
"email": "[email protected]",
domains: [
{
"name": "*.easegress.org",
"dnsProvider": {
"name": "dnspod",
"zone": "easegress.org",
"apiToken": "abc"
}
}
]
}
]`), nil
}
option = &HTTPProxyOptions{
AutoCert: true,
AutoCertDomainName: "*.easegress.example",
AutoCertDNSProvider: []string{
"name=aliyun",
"zone=easegress.com",
"apiToken=abc",
},
}
option.Complete([]string{"test"})
err = option.Parse()
assert.Nil(err)

spec, err = option.TranslateAutoCertManager()
assert.Nil(err)
assert.Equal("AutoCertManager", spec.Kind)
assert.Equal("autocert", spec.Name)
assert.Equal("[email protected]", spec.Email)

assert.Equal(2, len(spec.Domains))
assert.Equal("*.easegress.org", spec.Domains[0].Name)
assert.Equal("*.easegress.example", spec.Domains[1].Name)
}
33 changes: 33 additions & 0 deletions cmd/client/commandv2/specs/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/megaease/easegress/v2/pkg/filters"
"github.com/megaease/easegress/v2/pkg/filters/builder"
"github.com/megaease/easegress/v2/pkg/filters/proxies/httpproxy"
"github.com/megaease/easegress/v2/pkg/object/autocertmanager"
"github.com/megaease/easegress/v2/pkg/object/httpserver"
"github.com/megaease/easegress/v2/pkg/object/pipeline"
"github.com/megaease/easegress/v2/pkg/util/codectool"
Expand Down Expand Up @@ -79,6 +80,10 @@ func getDefaultPipelineSpec() *pipeline.Spec {
return (&pipeline.Pipeline{}).DefaultSpec().(*pipeline.Spec)
}

func getDefaultAutoCertManagerSpec() *autocertmanager.Spec {
return (&autocertmanager.AutoCertManager{}).DefaultSpec().(*autocertmanager.Spec)
}

// NewProxyFilterSpec returns a new ProxyFilterSpec.
func NewProxyFilterSpec(name string) *httpproxy.Spec {
spec := GetDefaultFilterSpec(httpproxy.Kind).(*httpproxy.Spec)
Expand Down Expand Up @@ -107,3 +112,31 @@ func NewRequestAdaptorFilterSpec(name string) *builder.RequestAdaptorSpec {
func GetDefaultFilterSpec(kind string) filters.Spec {
return filters.GetKind(kind).DefaultSpec()
}

// PipelineSpec is the spec of Pipeline.
type AutoCertManagerSpec struct {
Name string `json:"name"`
Kind string `json:"kind"`

autocertmanager.Spec `json:",inline"`
}

// NewAutoCertManagerSpec returns a new AutoCertManagerSpec.
func NewAutoCertManagerSpec() *AutoCertManagerSpec {
return &AutoCertManagerSpec{
Name: "autocertmanager",
Kind: autocertmanager.Kind,
Spec: *getDefaultAutoCertManagerSpec(),
}
}

// AddOrUpdateDomain adds or updates a domain.
func (a *AutoCertManagerSpec) AddOrUpdateDomain(domain *autocertmanager.DomainSpec) {
for i, d := range a.Domains {
if d.Name == domain.Name {
a.Domains[i] = *domain
return
}
}
a.Domains = append(a.Domains, *domain)
}

0 comments on commit b5f08e1

Please sign in to comment.