action/k8s/utils/http.go (139 lines of code) (raw):
package utils
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/seata/seata-ctl/tool"
"io"
"io/ioutil"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"net/http"
"strings"
)
type ContextType string
const (
Json ContextType = "application/json"
)
// ContextInfo is a structure to hold client certificate, key, CA certificate, API server URL, and content type.
type ContextInfo struct {
ClientCert string
ClientKey string
CACert string
APIServer string
ContentType ContextType
}
// LoadKubeConfig loads the kubeconfig from the provided path and filename
func LoadKubeConfig(kubeconfigFullPath string) (*api.Config, error) {
// Read the kubeconfig file
kubeconfigBytes, err := ioutil.ReadFile(kubeconfigFullPath)
if err != nil {
return nil, fmt.Errorf("failed to read kubeconfig file: %v", err)
}
// Convert the kubeconfig file content into the Config struct
config, err := clientcmd.Load(kubeconfigBytes)
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig: %v", err)
}
return config, nil
}
// GetContextInfo extracts the client certificate, key, CA certificate, API server URL, and content type
// from the provided *api.Config for the current context.
func GetContextInfo(config *api.Config) (*ContextInfo, error) {
// Get the current context name
currentContext := config.CurrentContext
if currentContext == "" {
return nil, fmt.Errorf("no current context is set")
}
// Get the current context object
context, ok := config.Contexts[currentContext]
if !ok {
return nil, fmt.Errorf("context %s not found", currentContext)
}
// Get the cluster object
cluster, ok := config.Clusters[context.Cluster]
if !ok {
return nil, fmt.Errorf("cluster %s not found", context.Cluster)
}
// Get the user object
authInfo, ok := config.AuthInfos[context.AuthInfo]
if !ok {
return nil, fmt.Errorf("auth info %s not found", context.AuthInfo)
}
// Extract the certificate and API server information
clientCert := authInfo.ClientCertificate
clientKey := authInfo.ClientKey
caCert := cluster.CertificateAuthority
apiServer := cluster.Server
// Content type for API request
contentType := Json
// Check if all required fields are present
if clientCert == "" || clientKey == "" || caCert == "" || apiServer == "" {
missingFields := []string{}
if clientCert == "" {
missingFields = append(missingFields, "client certificate")
}
if clientKey == "" {
missingFields = append(missingFields, "client key")
}
if caCert == "" {
missingFields = append(missingFields, "CA certificate")
}
if apiServer == "" {
missingFields = append(missingFields, "API server")
}
return nil, fmt.Errorf("missing required fields: %s", strings.Join(missingFields, ", "))
}
// Return the ContextInfo struct
return &ContextInfo{
ClientCert: clientCert,
ClientKey: clientKey,
CACert: caCert,
APIServer: apiServer,
ContentType: contentType,
}, nil
}
// sendPostRequest sends a POST request to the Kubernetes API server to create a custom resource definition (CRD)
func sendPostRequest(context *ContextInfo, createCrdPath string, filePath string) (string, error) {
certFile := context.ClientCert
keyFile := context.ClientKey
caCertFile := context.CACert
url := context.APIServer + createCrdPath
contentType := context.ContentType
// Load client certificate and key
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return "", fmt.Errorf("failed to load certificate and key: %v", err)
}
// Read CA certificate
caCert, err := ioutil.ReadFile(caCertFile)
if err != nil {
return "", fmt.Errorf("failed to read CA certificate: %v", err)
}
caCertPool := x509.NewCertPool()
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
return "", fmt.Errorf("failed to append CA certificate")
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
}
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
// Read data from file
data, err := ioutil.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("failed to read data file: %v", err)
}
// Create a POST request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
if err != nil {
return "", fmt.Errorf("failed to create request: %v", err)
}
// Set content type
req.Header.Set("Content-Type", string(contentType))
// Send the request
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to send request: %v", err)
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
tool.Logger.Error("failed to close response body: %v", err)
}
}(resp.Body)
// Read response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %v", err)
}
// Return appropriate response based on status code
if resp.StatusCode == http.StatusCreated {
tool.Logger.Infof("Create seata crd success")
return "", nil
}
if resp.StatusCode == http.StatusConflict {
return "", fmt.Errorf("seata crd already exists")
} else {
return "error: " + string(body), err
}
}