websoft9/docker/deployment/init_portainer.go
2024-12-11 14:08:58 +08:00

248 lines
7.3 KiB
Go

package main
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/big"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"time"
)
const (
portainerURL = "http://localhost:9000/api"
maxRetries = 5
retryDelay = 5 * time.Second
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@$()_"
credentialFilePath = "/data/credential"
initCheckURL = portainerURL + "/users/admin/check"
waitTimeout = 60 * time.Second
waitInterval = 2 * time.Second
)
type Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
// 启动并等待 Portainer 启动
cmd, err := startAndWaitForPortainer(os.Args[1:]...)
if err != nil {
log.Fatalf("Failed to start and wait for Portainer: %v", err)
}
// 检查是否已经初始化
if isPortainerInitialized() {
log.Println("Portainer is already initialized.")
// 等待 Portainer 进程结束
if err := cmd.Wait(); err != nil {
log.Fatalf("Portainer process exited with error: %v", err)
}
return
}
// 初始化 Portainer
adminUsername := "admin"
adminPassword := generateRandomPassword(12)
if err := initializePortainerUser(adminUsername, adminPassword); err != nil {
log.Fatalf("Failed to initialize Portainer user: %v", err)
} else {
if err := writeCredentialsToFile(adminPassword); err != nil {
log.Fatalf("Failed to write credentials to file: %v", err)
} else {
if err := initializeLocalEndpoint(adminUsername, adminPassword); err != nil {
log.Fatalf("Failed to initialize local endpoint: %v", err)
} else {
fmt.Println("Portainer initialization completed successfully.")
}
}
}
// 等待 Portainer 进程结束
if err := cmd.Wait(); err != nil {
log.Fatalf("Portainer process exited with error: %v", err)
}
}
func startAndWaitForPortainer(args ...string) (*exec.Cmd, error) {
cmd := exec.Command("/portainer", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start Portainer: %w", err)
}
timeout := time.After(waitTimeout)
ticker := time.NewTicker(waitInterval)
defer ticker.Stop()
for {
select {
case <-timeout:
return nil, fmt.Errorf("timeout waiting for Portainer")
case <-ticker.C:
resp, err := http.Get(portainerURL + "/system/status")
if err == nil && resp.StatusCode == http.StatusOK {
fmt.Println("Portainer is up……!")
return cmd, nil
}
fmt.Println("Waiting for Portainer...")
}
}
}
func generateRandomPassword(length int) string {
password := make([]byte, length)
for i := range password {
char, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
password[i] = charset[char.Int64()]
}
return string(password)
}
func initializePortainerUser(username, password string) error {
requestBody := Credentials{Username: username, Password: password}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("error marshaling request body: %w", err)
}
resp, err := retryRequest("POST", portainerURL+"/users/admin/init", "application/json", bytes.NewBuffer(jsonBody))
if err != nil {
return fmt.Errorf("error making request to Portainer API: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusConflict {
return nil
}
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("unexpected response status: %d, body: %s", resp.StatusCode, body)
}
return nil
}
func initializeLocalEndpoint(username, password string) error {
authBody := Credentials{Username: username, Password: password}
jsonBody, err := json.Marshal(authBody)
if err != nil {
return fmt.Errorf("error marshaling auth body: %w", err)
}
resp, err := retryRequest("POST", portainerURL+"/auth", "application/json", bytes.NewBuffer(jsonBody))
if err != nil {
return fmt.Errorf("error authenticating with Portainer API: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("unexpected response status: %d, body: %s", resp.StatusCode, body)
}
var authResult map[string]string
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error reading authentication response: %w", err)
}
err = json.Unmarshal(body, &authResult)
if err != nil {
return fmt.Errorf("error parsing authentication response: %w", err)
}
jwtToken := authResult["jwt"]
endpointBody := url.Values{}
endpointBody.Set("Name", "local")
endpointBody.Set("EndpointCreationType", "1")
req, err := http.NewRequest("POST", portainerURL+"/endpoints", strings.NewReader(endpointBody.Encode()))
if err != nil {
return fmt.Errorf("error creating endpoint request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Authorization", "Bearer "+jwtToken)
client := &http.Client{}
resp, err = client.Do(req)
if err != nil {
return fmt.Errorf("error creating endpoint in Portainer API: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusConflict {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("unexpected response status: %d, body: %s", resp.StatusCode, body)
}
if resp.StatusCode == http.StatusConflict {
fmt.Println("Endpoint already exists, but this is considered a success.")
} else {
fmt.Println("Endpoint created successfully.")
}
return nil
}
func writeCredentialsToFile(password string) error {
err := ioutil.WriteFile(credentialFilePath, []byte(password), 0755)
if err != nil {
return fmt.Errorf("error writing password to file: %w", err)
}
return nil
}
func retryRequest(method, url, contentType string, body *bytes.Buffer) (*http.Response, error) {
client := &http.Client{}
for i := 0; i < maxRetries; i++ {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Content-Type", contentType)
resp, err := client.Do(req)
if err == nil {
return resp, nil
}
log.Printf("Request failed: %v. Retrying in %v...", err, retryDelay)
time.Sleep(retryDelay)
}
return nil, fmt.Errorf("max retries reached")
}
func isPortainerInitialized() bool {
resp, err := http.Get(initCheckURL)
if err != nil {
log.Fatalf("Failed to check Portainer initialization status: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNoContent {
return true
}
if resp.StatusCode == http.StatusNotFound {
return false
}
log.Fatalf("Unexpected response status: %d", resp.StatusCode)
return false
}