feat(provider): add iBMC Redfish certificate deployer

This commit is contained in:
Frolov Dmitriy
2026-08-10 14:02:30 +03:00
committed by GitHub
parent 512e4ea61b
commit 0ca3b0ce82
26 changed files with 951 additions and 0 deletions
@@ -0,0 +1,27 @@
package deployers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/huaweiibmc"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.DeploymentProviderTypeHuaweiIBMC, func(options *ProviderFactoryOptions) (core.Deployer, error) {
credentials := domain.AccessConfigForHuaweiIBMC{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate Huawei iBMC access config: %w", err)
}
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
Host: credentials.Host,
Username: credentials.Username,
Password: credentials.Password,
AllowInsecureConnections: credentials.AllowInsecureConnections,
AutoRestart: xmaps.GetBool(options.ProviderExtendedConfig, "autoRestart"),
})
return provider, err
})
}
+7
View File
@@ -353,6 +353,13 @@ type AccessConfigForHuaweiCloud struct {
EnterpriseProjectId string `json:"enterpriseProjectId,omitempty"`
}
type AccessConfigForHuaweiIBMC struct {
Host string `json:"host"`
Username string `json:"username"`
Password string `json:"password"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForInfomaniak struct {
AccessToken string `json:"accessToken"`
}
+2
View File
@@ -77,6 +77,7 @@ const (
AccessProviderTypeHostingde = AccessProviderType("hostingde")
AccessProviderTypeHostinger = AccessProviderType("hostinger")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeHuaweiIBMC = AccessProviderType("huaweiibmc")
AccessProviderTypeInfomaniak = AccessProviderType("infomaniak")
AccessProviderTypeIONOS = AccessProviderType("ionos")
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
@@ -396,6 +397,7 @@ const (
DeploymentProviderTypeHuaweiCloudSCM = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-scm")
DeploymentProviderTypeHuaweiCloudVOD = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-vod")
DeploymentProviderTypeHuaweiCloudWAF = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-waf")
DeploymentProviderTypeHuaweiIBMC = DeploymentProviderType(AccessProviderTypeHuaweiIBMC)
DeploymentProviderTypeJDCloudALB = DeploymentProviderType(AccessProviderTypeJDCloud + "-alb")
DeploymentProviderTypeJDCloudCDN = DeploymentProviderType(AccessProviderTypeJDCloud + "-cdn")
DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live")
@@ -0,0 +1,171 @@
package huaweiibmc
import (
"context"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"fmt"
"log/slog"
"net"
"strings"
"github.com/certimate-go/certimate/pkg/core"
ibmcsdk "github.com/certimate-go/certimate/pkg/sdk3rd/huaweiibmc"
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
xloop "github.com/certimate-go/certimate/pkg/utils/loop"
)
type (
Provider = core.Deployer
DeployResult = core.DeployerDeployResult
)
type DeployerConfig struct {
// iBMC 主机。
Host string `json:"host"`
// iBMC 用户名。
Username string `json:"username"`
// iBMC 密码。
Password string `json:"password"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 是否自动重启。
AutoRestart bool `json:"autoRestart,omitempty"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
}
var _ Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the iBMC deployer is nil")
}
return &Deployer{
config: config,
logger: slog.Default(),
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
// 转换证书格式
certPFXPwd := make([]byte, 24)
if _, err := rand.Read(certPFXPwd); err != nil {
return nil, fmt.Errorf("failed to generate PFX password: %w", err)
}
certPFXPwdHex := hex.EncodeToString(certPFXPwd)
certPFX, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, certPFXPwdHex, nil)
if err != nil {
return nil, fmt.Errorf("failed to transform certificate from PEM to PFX: %w", err)
}
// 创建 iBMC 客户端
client, err := createSDKClient(d.config.Host, d.config.Username, d.config.Password, d.config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
// 创建会话
createSessionResp, err := client.CreateSessionWithContext(ctx)
d.logger.Debug("sdk request 'SessionService.CreateSession'", slog.Any("response", createSessionResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'SessionService.CreateSession': %w", err)
} else {
defer client.DeleteSessionWithContext(ctx)
}
// 查询管理集合资源信息
listManagersResp, err := client.ListManagersWithContext(ctx)
d.logger.Debug("sdk request 'Managers.ListManagers'", slog.Any("response", listManagersResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'Managers.ListManagers': %w", err)
}
// 批量更新管理证书
if len(listManagersResp.Members) == 0 {
d.logger.Info("no ibmc managers to deploy")
} else {
d.logger.Info("found ibmc managers to deploy", slog.Any("managers", listManagersResp.Members))
if err := xloop.ForRangeAllWithContext(ctx, listManagersResp.Members, func(ctx context.Context, managerInfo *ibmcsdk.Entity, _ int) error {
importCustomCertificateReq := &ibmcsdk.ImportCustomCertificateToManagerRequest{
ManagerID: managerInfo.ID,
ManagerLocation: managerInfo.ODataID,
Certificate: base64.StdEncoding.EncodeToString(certPFX),
Password: certPFXPwdHex,
}
importCustomCertificateResp, err := client.ImportCustomCertificateToManagerWithContext(ctx, importCustomCertificateReq)
d.logger.Debug("sdk request 'Managers.SecurityService.HttpsCert.ImportCustomCertificateToManager'", slog.String("params.managerODataId", managerInfo.ODataID), slog.Any("request", importCustomCertificateReq), slog.Any("response", importCustomCertificateResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'Managers.SecurityService.HttpsCert.ImportCustomCertificateToManager': %w", err)
}
if d.config.AutoRestart {
resetManagerReq := &ibmcsdk.ResetManagerRequest{
ManagerID: managerInfo.ID,
ManagerLocation: managerInfo.ODataID,
ResetType: "ForceRestart",
}
resetManagerResp, err := client.ResetManagerWithContext(ctx, resetManagerReq)
d.logger.Debug("sdk request 'Managers.ResetManager'", slog.String("params.managerODataId", managerInfo.ODataID), slog.Any("request", resetManagerReq), slog.Any("response", resetManagerResp))
if err != nil {
return fmt.Errorf("failed to execute sdk request 'Managers.ResetManager': %w", err)
}
}
return nil
}); err != nil {
return nil, err
}
}
return &DeployResult{}, nil
}
func createSDKClient(host, username, password string, skipTlsVerify bool) (*ibmcsdk.Client, error) {
serverUrl := ""
if strings.Contains(host, "://") {
serverUrl = host
} else {
if net.ParseIP(host) != nil && strings.Contains(host, ":") {
host = "[" + host + "]"
}
serverUrl = "https://" + host
}
client, err := ibmcsdk.NewClient(
serverUrl,
ibmcsdk.WithLogins(username, password),
)
if err != nil {
return nil, err
}
if skipTlsVerify {
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}
func generateCertificatePassword() (string, error) {
password := make([]byte, 24)
if _, err := rand.Read(password); err != nil {
return "", err
}
return hex.EncodeToString(password), nil
}
@@ -0,0 +1,53 @@
package huaweiibmc_test
import (
"testing"
"github.com/stretchr/testify/require"
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/huaweiibmc"
it "github.com/certimate-go/certimate/pkg/core/deployer/testing"
)
var (
fp = it.Args("HUAWEIIBMC_")
fTestCertPath string
fTestKeyPath string
fHost string
fUsername string
fPassword string
)
func init() {
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
fp.DefineString(&fHost, "HOST")
fp.DefineString(&fUsername, "USERNAME")
fp.DefineString(&fPassword, "PASSWORD")
}
/*
Shell command to run this test:
go test -v ./huaweiibmc_test.go -args \
--HUAWEIIBMC_TESTCERTPATH="/path/to/your-test-cert.pem" \
--HUAWEIIBMC_TESTKEYPATH="/path/to/your-test-key.pem" \
--HUAWEIIBMC_HOST="localhost" \
--HUAWEIIBMC_USERNAME="admin" \
--HUAWEIIBMC_PASSWORD="password"
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Deploy", func(t *testing.T) {
provider, err := impl.NewDeployer(&impl.DeployerConfig{
Host: fHost,
Username: fUsername,
Password: fPassword,
AutoRestart: true,
})
require.NoError(t, err)
it.TestDeploy(t, provider, it.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
})
}
@@ -0,0 +1,53 @@
package huaweiibmc
import (
"context"
"net/http"
"net/url"
"regexp"
)
type GetManagersResponse struct {
sdkResponseBase
Members []*Entity `json:"Members"`
}
type ListManagersResponse struct {
sdkResponseBase
Members []*Entity `json:"Members"`
}
func (c *Client) ListManagers() (*ListManagersResponse, error) {
return c.ListManagersWithContext(context.Background())
}
func (c *Client) ListManagersWithContext(ctx context.Context) (*ListManagersResponse, error) {
httpreq, err := c.newRequest(http.MethodGet, "/redfish/v1/Managers")
if err != nil {
return nil, err
} else {
httpreq.SetContext(ctx)
}
result := &ListManagersResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
} else {
for _, member := range result.Members {
if member.ODataID == "" && member.ID != "" {
member.ODataID = "/redfish/v1/Managers/" + url.PathEscape(member.ID)
}
if member.ID == "" && member.ODataID != "" {
re := regexp.MustCompile(`/redfish/v1/Managers/([^/]+)$`)
matches := re.FindStringSubmatch(member.ODataID)
if len(matches) == 2 {
member.ID = matches[1]
}
}
}
}
return result, nil
}
@@ -0,0 +1,80 @@
package huaweiibmc
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
type ResetManagerRequest struct {
sdkResponseBase
ManagerID string `json:"-"`
ManagerLocation string `json:"-"`
ResetType string `json:"ResetType,omitempty"`
}
type ResetManagerResponse struct {
sdkResponseBase
}
func (r *ResetManagerResponse) GetAPIError() error {
if r.isSuccessfulResponse() {
return nil
}
return r.sdkResponseBase.GetAPIError()
}
func (r *ResetManagerResponse) isSuccessfulResponse() bool {
if r.Error == nil {
return false
}
var extendedInfo []struct {
MessageID string `json:"MessageId"`
}
if err := json.Unmarshal(r.Error.MessageExtendedInfo, &extendedInfo); err != nil {
return false
}
for _, info := range extendedInfo {
if info.MessageID == "Base.1.0.Success" {
return true
}
}
return false
}
func (c *Client) ResetManager(req *ResetManagerRequest) (*ResetManagerResponse, error) {
return c.ResetManagerWithContext(context.Background(), req)
}
func (c *Client) ResetManagerWithContext(ctx context.Context, req *ResetManagerRequest) (*ResetManagerResponse, error) {
managerLoc := strings.TrimRight(req.ManagerLocation, "/")
if managerLoc == "" && req.ManagerID != "" {
managerLoc = "/redfish/v1/Managers/" + url.PathEscape(req.ManagerID)
}
if managerLoc == "" {
return nil, fmt.Errorf("sdkerr: bad request: unset managerId")
}
httpreq, err := c.newRequest(http.MethodPost, managerLoc+"/Actions/Manager.Reset")
if err != nil {
return nil, err
} else {
httpreq.SetBody(req)
httpreq.SetContext(ctx)
}
result := &ResetManagerResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}
@@ -0,0 +1,81 @@
package huaweiibmc
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
type ImportCustomCertificateToManagerRequest struct {
sdkResponseBase
ManagerID string `json:"-"`
ManagerLocation string `json:"-"`
Certificate string `json:"Certificate,omitempty"`
Password string `json:"Password,omitempty"`
}
type ImportCustomCertificateToManagerResponse struct {
sdkResponseBase
}
func (r *ImportCustomCertificateToManagerResponse) GetAPIError() error {
if r.isSuccessfulResponse() {
return nil
}
return r.sdkResponseBase.GetAPIError()
}
func (r *ImportCustomCertificateToManagerResponse) isSuccessfulResponse() bool {
if r.Error == nil {
return false
}
var extendedInfo []struct {
MessageID string `json:"MessageId"`
}
if err := json.Unmarshal(r.Error.MessageExtendedInfo, &extendedInfo); err != nil {
return false
}
for _, info := range extendedInfo {
if info.MessageID == "iBMC.1.0.CertImportOK" {
return true
}
}
return false
}
func (c *Client) ImportCustomCertificateToManager(req *ImportCustomCertificateToManagerRequest) (*ImportCustomCertificateToManagerResponse, error) {
return c.ImportCustomCertificateToManagerWithContext(context.Background(), req)
}
func (c *Client) ImportCustomCertificateToManagerWithContext(ctx context.Context, req *ImportCustomCertificateToManagerRequest) (*ImportCustomCertificateToManagerResponse, error) {
managerLoc := strings.TrimRight(req.ManagerLocation, "/")
if managerLoc == "" && req.ManagerID != "" {
managerLoc = "/redfish/v1/Managers/" + url.PathEscape(req.ManagerID)
}
if managerLoc == "" {
return nil, fmt.Errorf("sdkerr: bad request: unset managerId")
}
httpreq, err := c.newRequest(http.MethodPost, managerLoc+"/SecurityService/HttpsCert/Actions/HttpsCert.ImportCustomCertificate")
if err != nil {
return nil, err
} else {
httpreq.SetBody(req)
httpreq.SetContext(ctx)
}
result := &ImportCustomCertificateToManagerResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}
@@ -0,0 +1,60 @@
package huaweiibmc
import (
"context"
"fmt"
"net/http"
)
type CreateSessionResponse struct {
sdkResponseBase
XToken string `json:"-"`
XLocation string `json:"-"`
}
func (c *Client) CreateSession() (*CreateSessionResponse, error) {
return c.CreateSessionWithContext(context.Background())
}
func (c *Client) CreateSessionWithContext(ctx context.Context) (*CreateSessionResponse, error) {
c.tokenMu.Lock()
if c.token != "" {
c.tokenMu.Unlock()
return nil, fmt.Errorf("sdkerr: auth error: session already created")
}
c.tokenMu.Unlock()
httpreq, err := c.newRequest(http.MethodPost, "/redfish/v1/SessionService/Sessions")
if err != nil {
return nil, err
} else {
httpreq.SetBody(map[string]string{
"UserName": c.username,
"Password": c.password,
})
httpreq.SetContext(ctx)
}
result := &CreateSessionResponse{}
if httpresp, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
} else {
token := httpresp.Header().Get("X-Auth-Token")
location := httpresp.Header().Get("Location")
result.XToken = token
result.XLocation = c.resolveLocation(location)
if result.XToken == "" {
return result, fmt.Errorf("sdkerr: auth error: received empty token")
}
c.tokenMu.Lock()
c.token = result.XToken
c.tokenLoc = result.XLocation
c.tokenMu.Unlock()
}
return result, nil
}
@@ -0,0 +1,48 @@
package huaweiibmc
import (
"context"
"fmt"
"net/http"
)
type DeleteSessionResponse struct {
sdkResponseBase
}
func (c *Client) DeleteSession() (*DeleteSessionResponse, error) {
return c.DeleteSessionWithContext(context.Background())
}
func (c *Client) DeleteSessionWithContext(ctx context.Context) (*DeleteSessionResponse, error) {
c.tokenMu.Lock()
if c.token == "" {
c.tokenMu.Unlock()
return nil, fmt.Errorf("sdkerr: auth error: session not created")
}
if c.tokenLoc == "" {
c.tokenMu.Unlock()
return &DeleteSessionResponse{}, nil
}
c.tokenMu.Unlock()
httpreq, err := c.newRequest(http.MethodDelete, c.tokenLoc)
if err != nil {
return nil, err
} else {
httpreq.SetContext(ctx)
}
result := &DeleteSessionResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
} else {
c.tokenMu.Lock()
c.token = ""
c.tokenLoc = ""
c.tokenMu.Unlock()
}
return result, nil
}
+156
View File
@@ -0,0 +1,156 @@
// A simple SDK client for Huawei iBMC Redfish.
package huaweiibmc
import (
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/go-resty/resty/v2"
"github.com/certimate-go/certimate/internal/app"
)
type Client struct {
username string
password string
token string
tokenLoc string
tokenMu sync.Mutex
rc *resty.Client
}
type sdkSuccessfulResponse interface {
isSuccessfulResponse() bool
}
func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
opts := &Options{}
for _, fn := range optFns {
fn(opts)
}
if serverUrl == "" {
return nil, fmt.Errorf("sdkerr: unset serverUrl")
}
if _, err := url.Parse(serverUrl); err != nil {
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
}
if opts.Username == "" {
return nil, fmt.Errorf("sdkerr: unset username")
}
if opts.Password == "" {
return nil, fmt.Errorf("sdkerr: unset password")
}
client := &Client{
username: opts.Username,
password: opts.Password,
}
client.rc = resty.New().
SetBaseURL(strings.TrimRight(serverUrl, "/")).
SetHeader("Accept", "application/json").
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(_ *resty.Client, req *http.Request) error {
if client.token != "" {
req.Header.Set("X-Auth-Token", client.token)
}
return nil
})
return client, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.rc.SetTimeout(timeout)
return c
}
func (c *Client) SetTLSConfig(config *tls.Config) *Client {
c.rc.SetTLSClientConfig(config)
return c
}
func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
if method == "" {
return nil, fmt.Errorf("sdkerr: unset method")
}
if path == "" {
return nil, fmt.Errorf("sdkerr: unset path")
}
req := c.rc.R()
req.Method = method
req.URL = path
// WARN:
// DO NOT CALL `req.SetResult` or `req.SetError` AGAIN! USE `doRequestWithResult` INSTEAD.
return req, nil
}
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
}
return resp, nil
}
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
resp, err := c.doRequest(req)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &res)
if successful, ok := res.(sdkSuccessfulResponse); ok && successful.isSuccessfulResponse() {
return resp, nil
}
}
return resp, err
}
if len(resp.Body()) != 0 {
if err := json.Unmarshal(resp.Body(), &res); err != nil {
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
} else {
if err := res.GetAPIError(); err != nil {
return resp, fmt.Errorf("sdkerr: api error: %s", err.Error())
}
}
}
return resp, nil
}
func (c *Client) resolveLocation(location string) string {
u, err := url.Parse(location)
if err != nil || u.IsAbs() {
return location
}
base, err := url.Parse(c.rc.BaseURL)
if err != nil {
return location
}
return base.ResolveReference(u).String()
}
+9
View File
@@ -0,0 +1,9 @@
package huaweiibmc
type Entity struct {
ID string `json:"Id,omitempty"`
ODataID string `json:"@odata.id,omitempty"`
ODataType string `json:"@odata.type,omitempty"`
Name string `json:"Name,omitempty"`
Description string `json:"Description"`
}
+15
View File
@@ -0,0 +1,15 @@
package huaweiibmc
type Options struct {
Username string
Password string
}
type OptionsFunc func(*Options)
func WithLogins(username, password string) OptionsFunc {
return func(o *Options) {
o.Username = username
o.Password = password
}
}
+34
View File
@@ -0,0 +1,34 @@
package huaweiibmc
import (
"encoding/json"
"fmt"
)
type sdkResponse interface {
GetAPIError() error
}
type sdkResponseBase struct {
ODataContext string `json:"@odata.context,omitempty"`
Error *sdkAPIError `json:"error,omitempty"`
}
type sdkAPIError struct {
Code string `json:"code"`
Message string `json:"message"`
MessageExtendedInfo json.RawMessage `json:"@Message.ExtendedInfo,omitempty"`
}
func (e sdkAPIError) Error() string {
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
func (r *sdkResponseBase) GetAPIError() error {
if r.Error != nil {
return *r.Error
}
return nil
}
var _ sdkResponse = (*sdkResponseBase)(nil)
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 1027 1024"><path fill="#c71f1e" d="M378.88 143.36c20.48-6.827 40.96-10.24 61.44-13.653 23.893 30.72 30.72 71.68 40.96 109.226 6.827 40.96 13.653 81.92 13.653 122.88 6.827 27.307 3.414 58.027 3.414 85.334s-3.414 51.2 0 78.506c0 37.547-3.414 71.68-3.414 109.227-6.826 23.893 0 47.787-6.826 71.68-10.24-3.413-13.654-13.653-17.067-20.48-40.96-61.44-78.507-122.88-112.64-187.733-34.133-68.267-68.267-136.534-71.68-215.04-6.827-61.44 34.133-119.467 92.16-139.947m211.627-10.24c6.826-3.413 10.24 0 17.066 0 27.307 6.827 58.027 10.24 81.92 27.307s44.374 40.96 51.2 68.266c10.24 34.134 6.827 71.68 0 105.814-10.24 44.373-30.72 85.333-51.2 126.293-6.826 13.653-13.653 23.893-20.48 37.547-10.24 23.893-27.306 47.786-40.96 71.68-23.893 40.96-47.786 75.093-71.68 116.053-3.413 3.413-6.826 13.653-13.653 6.827-3.413-40.96-6.827-81.92-10.24-126.294-6.827-54.613-3.413-105.813-3.413-160.426 3.413-34.134 3.413-71.68 6.826-105.814 6.827-40.96 13.654-85.333 27.307-126.293 13.653-10.24 13.653-30.72 27.307-40.96m-430.08 133.12c3.413 0 6.826 6.827 10.24 10.24C269.653 406.187 358.4 542.72 430.08 689.493c6.827 10.24 13.653 23.894 13.653 37.547-13.653-3.413-23.893-10.24-34.133-17.067-64.853-34.133-129.707-71.68-194.56-109.226-23.893-17.067-47.787-34.134-68.267-51.2-40.96-27.307-68.266-78.507-64.853-129.707 3.413-61.44 37.547-112.64 78.507-153.6m706.56 0h6.826c17.067 23.893 40.96 47.787 54.614 75.093 13.653 23.894 20.48 54.614 23.893 81.92 0 30.72-10.24 64.854-34.133 88.747-13.654 13.653-23.894 27.307-40.96 37.547-78.507 61.44-163.84 109.226-252.587 153.6-13.653 6.826-23.893 17.066-40.96 17.066 3.413-17.066 13.653-34.133 20.48-47.786C662.187 552.96 733.867 440.32 812.373 331.093c17.067-17.066 37.547-40.96 54.614-64.853M10.24 539.307c3.413-3.414 0-10.24 6.827-13.654 10.24 3.414 20.48 10.24 27.306 13.654 122.88 68.266 245.76 136.533 365.227 211.626 3.413 3.414 6.827 6.827 6.827 10.24h-235.52c-47.787 0-92.16-20.48-126.294-54.613C27.307 675.84 3.413 634.88 0 593.92c6.827-17.067 3.413-34.133 10.24-54.613m983.04-3.414c6.827-3.413 17.067-10.24 23.893-6.826 0 17.066 6.827 37.546 6.827 54.613-3.413 23.893-3.413 44.373-13.653 64.853-6.827 17.067-17.067 37.547-30.72 51.2-17.067 13.654-27.307 30.72-47.787 40.96-20.48 17.067-51.2 20.48-75.093 23.894h-245.76c3.413-3.414 3.413-6.827 6.826-10.24C740.693 675.84 866.987 604.16 993.28 535.893M184.32 798.72c44.373-3.413 88.747 0 133.12-6.827 30.72 0 64.853-3.413 95.573 0-6.826 13.654-23.893 20.48-34.133 27.307-34.133 23.893-68.267 44.373-105.813 61.44s-81.92 10.24-112.64-13.653c-23.894-17.067-44.374-44.374-58.027-68.267zm433.493-6.827c30.72-3.413 61.44 0 95.574 0 40.96 3.414 85.333 0 129.706 6.827 30.72 3.413 61.44 0 88.747 3.413-10.24 20.48-27.307 40.96-44.373 58.027-27.307 27.307-68.267 40.96-105.814 34.133-34.133-10.24-61.44-30.72-92.16-47.786-17.066-10.24-30.72-20.48-47.786-30.72-10.24-10.24-17.067-13.654-23.894-23.894"/></svg>
@@ -64,6 +64,7 @@ import AccessConfigFieldsProviderHetzner from "./AccessConfigFieldsProviderHetzn
import AccessConfigFieldsProviderHostingde from "./AccessConfigFieldsProviderHostingde";
import AccessConfigFieldsProviderHostinger from "./AccessConfigFieldsProviderHostinger";
import AccessConfigFieldsProviderHuaweiCloud from "./AccessConfigFieldsProviderHuaweiCloud";
import AccessConfigFieldsProviderHuaweiIBMC from "./AccessConfigFieldsProviderHuaweiIBMC";
import AccessConfigFieldsProviderInfomaniak from "./AccessConfigFieldsProviderInfomaniak";
import AccessConfigFieldsProviderIONOS from "./AccessConfigFieldsProviderIONOS";
import AccessConfigFieldsProviderJDCloud from "./AccessConfigFieldsProviderJDCloud";
@@ -193,6 +194,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
[ACCESS_PROVIDERS.HOSTINGDE]: AccessConfigFieldsProviderHostingde,
[ACCESS_PROVIDERS.HOSTINGER]: AccessConfigFieldsProviderHostinger,
[ACCESS_PROVIDERS.HUAWEICLOUD]: AccessConfigFieldsProviderHuaweiCloud,
[ACCESS_PROVIDERS.HUAWEIIBMC]: AccessConfigFieldsProviderHuaweiIBMC,
[ACCESS_PROVIDERS.IONOS]: AccessConfigFieldsProviderIONOS,
[ACCESS_PROVIDERS.JDCLOUD]: AccessConfigFieldsProviderJDCloud,
[ACCESS_PROVIDERS.KONG]: AccessConfigFieldsProviderKong,
@@ -0,0 +1,73 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { isHostname, isUrlWithHttpOrHttps } from "@/utils/validator";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFieldsProviderHuaweiIBMC = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({ [parentNamePath]: getSchema({ i18n }) });
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item name={[parentNamePath, "host"]} initialValue={initialValues.host} label={t("access.form.huaweiibmc_host.label")} rules={[formRule]}>
<Input placeholder={t("access.form.huaweiibmc_host.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "username"]}
initialValue={initialValues.username}
label={t("access.form.huaweiibmc_username.label")}
rules={[formRule]}
>
<Input autoComplete="new-password" placeholder={t("access.form.huaweiibmc_username.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "password"]}
initialValue={initialValues.password}
label={t("access.form.huaweiibmc_password.label")}
rules={[formRule]}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.huaweiibmc_password.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "allowInsecureConnections"]}
initialValue={initialValues.allowInsecureConnections}
label={t("access.form.shared_allow_insecure_conns.label")}
rules={[formRule]}
>
<Switch />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
host: "",
username: "",
password: "",
allowInsecureConnections: true,
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
host: z.string().refine((v) => isHostname(v) || isUrlWithHttpOrHttps(v), t("common.errmsg.host_invalid")),
username: z.string().nonempty(),
password: z.string().nonempty(),
allowInsecureConnections: z.boolean().nullish(),
});
};
export default Object.assign(AccessConfigFieldsProviderHuaweiIBMC, { getInitialValues, getSchema });
@@ -74,6 +74,7 @@ import BizDeployNodeConfigFieldsProviderHuaweiCloudLive from "./BizDeployNodeCon
import BizDeployNodeConfigFieldsProviderHuaweiCloudOBS from "./BizDeployNodeConfigFieldsProviderHuaweiCloudOBS";
import BizDeployNodeConfigFieldsProviderHuaweiCloudVOD from "./BizDeployNodeConfigFieldsProviderHuaweiCloudVOD";
import BizDeployNodeConfigFieldsProviderHuaweiCloudWAF from "./BizDeployNodeConfigFieldsProviderHuaweiCloudWAF";
import BizDeployNodeConfigFieldsProviderHuaweiIBMC from "./BizDeployNodeConfigFieldsProviderHuaweiIBMC";
import BizDeployNodeConfigFieldsProviderJDCloudALB from "./BizDeployNodeConfigFieldsProviderJDCloudALB";
import BizDeployNodeConfigFieldsProviderJDCloudCDN from "./BizDeployNodeConfigFieldsProviderJDCloudCDN";
import BizDeployNodeConfigFieldsProviderJDCloudLive from "./BizDeployNodeConfigFieldsProviderJDCloudLive";
@@ -226,6 +227,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_OBS]: BizDeployNodeConfigFieldsProviderHuaweiCloudOBS,
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_VOD]: BizDeployNodeConfigFieldsProviderHuaweiCloudVOD,
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_WAF]: BizDeployNodeConfigFieldsProviderHuaweiCloudWAF,
[DEPLOYMENT_PROVIDERS.HUAWEIIBMC]: BizDeployNodeConfigFieldsProviderHuaweiIBMC,
[DEPLOYMENT_PROVIDERS.JDCLOUD_ALB]: BizDeployNodeConfigFieldsProviderJDCloudALB,
[DEPLOYMENT_PROVIDERS.JDCLOUD_CDN]: BizDeployNodeConfigFieldsProviderJDCloudCDN,
[DEPLOYMENT_PROVIDERS.JDCLOUD_LIVE]: BizDeployNodeConfigFieldsProviderJDCloudLive,
@@ -0,0 +1,41 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const BizDeployNodeConfigFieldsProviderHuaweiIBMC = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({ [parentNamePath]: getSchema({ i18n }) });
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<Form.Item
name={[parentNamePath, "autoRestart"]}
initialValue={initialValues.autoRestart}
label={t("workflow_node.deploy.form.huaweiibmc_auto_restart.label")}
rules={[formRule]}
>
<Switch />
</Form.Item>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
autoRestart: true,
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t: _ } = i18n;
return z.object({
autoRestart: z.boolean().nullish(),
});
};
export default Object.assign(BizDeployNodeConfigFieldsProviderHuaweiIBMC, { getInitialValues, getSchema });
+4
View File
@@ -79,6 +79,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
HOSTINGDE: "hostingde",
HOSTINGER: "hostinger",
HUAWEICLOUD: "huaweicloud",
HUAWEIIBMC: "huaweiibmc",
INFOMANIAK: "infomaniak",
IONOS: "ionos",
JDCLOUD: "jdcloud",
@@ -230,6 +231,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.SAFELINE, "provider.safeline", "/imgs/providers/safeline.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.SAMWAF, "provider.samwaf", "/imgs/providers/samwaf.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.SYNOLOGYDSM, "provider.synologydsm", "/imgs/providers/synologydsm.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.HUAWEIIBMC, "provider.huaweiibmc", "/imgs/providers/huawei.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.AKAMAI, "provider.akamai", "/imgs/providers/akamai.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.ARVANCLOUD, "provider.arvancloud", "/imgs/providers/arvancloud.svg", [ACCESS_USAGES.DNS]],
@@ -692,6 +694,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
HUAWEICLOUD_SCM: `${ACCESS_PROVIDERS.HUAWEICLOUD}-scm`,
HUAWEICLOUD_VOD: `${ACCESS_PROVIDERS.HUAWEICLOUD}-vod`,
HUAWEICLOUD_WAF: `${ACCESS_PROVIDERS.HUAWEICLOUD}-waf`,
HUAWEIIBMC: `${ACCESS_PROVIDERS.HUAWEIIBMC}`,
JDCLOUD_ALB: `${ACCESS_PROVIDERS.JDCLOUD}-alb`,
JDCLOUD_CDN: `${ACCESS_PROVIDERS.JDCLOUD}-cdn`,
JDCLOUD_LIVE: `${ACCESS_PROVIDERS.JDCLOUD}-live`,
@@ -960,6 +963,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.PROXMOXBS, "provider.proxmoxbs", DEPLOYMENT_CATEGORIES.OTHER],
[DEPLOYMENT_PROVIDERS.PROXMOXVE, "provider.proxmoxve", DEPLOYMENT_CATEGORIES.OTHER],
[DEPLOYMENT_PROVIDERS.SYNOLOGYDSM, "provider.synologydsm", DEPLOYMENT_CATEGORIES.OTHER],
[DEPLOYMENT_PROVIDERS.HUAWEIIBMC, "provider.huaweiibmc", DEPLOYMENT_CATEGORIES.OTHER],
] satisfies Array<[DeploymentProviderType, string, DeploymentCategoryType, "builtin"] | [DeploymentProviderType, string, DeploymentCategoryType]>
).map(([type, name, category, builtin]) => [
type,
+12
View File
@@ -796,6 +796,18 @@
"placeholder": "Please enter Huawei Cloud enterprise project ID",
"tooltip": "For more information, see <a href=\"https://support.huaweicloud.com/intl/en-us/usermanual-em/em_03_0000.html\" target=\"_blank\">https://support.huaweicloud.com/intl/en-us/usermanual-em/em_03_0000.html</a>"
},
"huaweiibmc_host": {
"label": "iBMC Redfish server host",
"placeholder": "Please enter iBMC Redfish server host"
},
"huaweiibmc_username": {
"label": "iBMC username",
"placeholder": "Please enter the iBMC username"
},
"huaweiibmc_password": {
"label": "iBMC password",
"placeholder": "Please enter the iBMC password"
},
"infomaniak_access_token": {
"label": "Infomaniak access token",
"placeholder": "Please enter Infomaniak access token",
@@ -171,6 +171,7 @@
"huaweicloud_scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)",
"huaweicloud_vod": "Huawei Cloud - VOD (Video on Demand)",
"huaweicloud_waf": "Huawei Cloud - WAF (Web Application Firewall)",
"huaweiibmc": "Huawei iBMC",
"infomaniak": "Infomaniak",
"ionos": "IONOS",
"jdcloud": "JD Cloud",
@@ -1717,6 +1717,9 @@
"placeholder": "Please enter Huawei Cloud WAF certificate ID",
"tooltip": "For more information, see <a href=\"https://console-intl.huaweicloud.com/console/#/waf/certificateManagement\" target=\"_blank\">https://console-intl.huaweicloud.com/console/#/waf/certificateManagement</a>"
},
"huaweiibmc_auto_restart": {
"label": "Auto restart iBMC after deployment"
},
"jdcloud_alb_region_id": {
"label": "JD Cloud region ID",
"placeholder": "Please enter JD Cloud ALB region ID (e.g. cn-north-1)",
+12
View File
@@ -796,6 +796,18 @@
"placeholder": "请输入华为云企业项目 ID",
"tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/usermanual-em/zh-cn_topic_0126101490.html\" target=\"_blank\">https://support.huaweicloud.com/usermanual-em/zh-cn_topic_0126101490.html</a>"
},
"huaweiibmc_host": {
"label": "iBMC Redfish 服务器地址",
"placeholder": "请输入 iBMC Redfish 服务器地址"
},
"huaweiibmc_username": {
"label": "iBMC 用户名",
"placeholder": "请输入 iBMC 用户名"
},
"huaweiibmc_password": {
"label": "iBMC 密码",
"placeholder": "请输入 iBMC 密码"
},
"infomaniak_access_token": {
"label": "Infomaniak AccessToken",
"placeholder": "请输入 Infomaniak AccessToken",
@@ -171,6 +171,7 @@
"huaweicloud_scm_upload": "华为云 - 上传到云证书管理服务 SCM",
"huaweicloud_vod": "华为云 - 视频点播 VOD",
"huaweicloud_waf": "华为云 - Web 应用防火墙 WAF",
"huaweiibmc": "华为 iBMC",
"infomaniak": "Infomaniak",
"ionos": "IONOS",
"jdcloud": "京东云",
@@ -1716,6 +1716,9 @@
"placeholder": "请输入华为云 WAF 证书 ID",
"tooltip": "这是什么?请参阅 <a href=\"https://console.huaweicloud.com/console/#/waf/certificateManagement\" target=\"_blank\">https://console.huaweicloud.com/console/#/waf/certificateManagement</a>"
},
"huaweiibmc_auto_restart": {
"label": "部署后自动重启 iBMC 服务"
},
"jdcloud_alb_region_id": {
"label": "京东云服务地域 ID",
"placeholder": "请输入京东云 ALB 服务地域 ID(例如:cn-north-1",