chore: update main.go
This commit is contained in:
35
action.yml
35
action.yml
@@ -45,15 +45,26 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: "docker"
|
using: "docker"
|
||||||
image: "Dockerfile"
|
image: "Dockerfile"
|
||||||
env:
|
args:
|
||||||
PORTAINER_URL: ${{ inputs.portainer-url }}
|
- "--url"
|
||||||
PORTAINER_TOKEN: ${{ inputs.portainer-token }}
|
- "${{ inputs.portainer-url }}"
|
||||||
PORTAINER_ENDPOINT: ${{ inputs.portainer-endpoint }}
|
- "--token"
|
||||||
STACK_NAME: ${{ inputs.stack-name }}
|
- "${{ inputs.portainer-token }}"
|
||||||
REPO_URL: ${{ inputs.repo-url }}
|
- "--endpoint"
|
||||||
REPO_REF: ${{ inputs.repo-ref }}
|
- "${{ inputs.portainer-endpoint }}"
|
||||||
REPO_COMPOSE_FILE: ${{ inputs.repo-compose-file }}
|
- "--stack"
|
||||||
REPO_USERNAME: ${{ inputs.repo-username }}
|
- "${{ inputs.stack-name }}"
|
||||||
REPO_PASSWORD: ${{ inputs.repo-password }}
|
- "--repo-url"
|
||||||
TLS_SKIP_VERIFY: ${{ inputs.tls-skip-verify }}
|
- "${{ inputs.repo-url }}"
|
||||||
ENV_DATA: ${{ inputs.env-data }}
|
- "--repo-ref"
|
||||||
|
- "${{ inputs.repo-ref }}"
|
||||||
|
- "--compose"
|
||||||
|
- "${{ inputs.repo-compose-file }}"
|
||||||
|
- "--repo-user"
|
||||||
|
- "${{ inputs.repo-username }}"
|
||||||
|
- "--repo-pass"
|
||||||
|
- "${{ inputs.repo-password }}"
|
||||||
|
- "--insecure"
|
||||||
|
- "${{ inputs.tls-skip-verify }}"
|
||||||
|
- "--env"
|
||||||
|
- "${{ inputs.env-data }}"
|
||||||
|
|||||||
364
main.go
364
main.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -12,51 +13,115 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// CLI configuration
|
||||||
|
// -------------------------------
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
PortainerURL string
|
||||||
|
PortainerToken string
|
||||||
|
EndpointID int
|
||||||
|
StackName string
|
||||||
|
RepoURL string
|
||||||
|
RepoRef string
|
||||||
|
RepoComposeFile string
|
||||||
|
RepoUsername string
|
||||||
|
RepoPassword string
|
||||||
|
TLSSkipVerify bool
|
||||||
|
EnvData string
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// Data structures
|
// Data structures
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
|
|
||||||
|
type Stacks []Stack
|
||||||
|
|
||||||
type Stack struct {
|
type Stack struct {
|
||||||
ID int `json:"Id"`
|
AdditionalFiles []string `json:"AdditionalFiles"`
|
||||||
|
AutoUpdate AutoUpdate `json:"AutoUpdate"`
|
||||||
|
EndpointID int64 `json:"EndpointId"`
|
||||||
|
EntryPoint string `json:"EntryPoint"`
|
||||||
|
Env []Env `json:"Env"`
|
||||||
|
ID int64 `json:"Id"`
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
EndpointId int `json:"EndpointId"`
|
Option Option `json:"Option"`
|
||||||
|
ResourceControl ResourceControl `json:"ResourceControl"`
|
||||||
|
Status int64 `json:"Status"`
|
||||||
|
SwarmID string `json:"SwarmId"`
|
||||||
|
Type int64 `json:"Type"`
|
||||||
|
CreatedBy string `json:"createdBy"`
|
||||||
|
CreationDate int64 `json:"creationDate"`
|
||||||
|
FromAppTemplate bool `json:"fromAppTemplate"`
|
||||||
GitConfig GitConfig `json:"gitConfig"`
|
GitConfig GitConfig `json:"gitConfig"`
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
ProjectPath string `json:"projectPath"`
|
||||||
|
UpdateDate int64 `json:"updateDate"`
|
||||||
|
UpdatedBy string `json:"updatedBy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitConfig struct {
|
type AutoUpdate struct {
|
||||||
URL string `json:"url"`
|
ForcePullImage bool `json:"forcePullImage"`
|
||||||
ReferenceName string `json:"referenceName"`
|
ForceUpdate bool `json:"forceUpdate"`
|
||||||
ConfigFilePath string `json:"configFilePath"`
|
Interval string `json:"interval"`
|
||||||
|
JobID string `json:"jobID"`
|
||||||
|
Webhook string `json:"webhook"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnvVar struct {
|
type Env struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------
|
type GitConfig struct {
|
||||||
// Environment-driven configuration
|
Authentication Authentication `json:"authentication"`
|
||||||
// -------------------------------
|
ConfigFilePath string `json:"configFilePath"`
|
||||||
|
ConfigHash string `json:"configHash"`
|
||||||
|
ReferenceName string `json:"referenceName"`
|
||||||
|
TlsskipVerify bool `json:"tlsskipVerify"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
type Authentication struct {
|
||||||
portainerURL = os.Getenv("PORTAINER_URL")
|
AuthorizationType int64 `json:"authorizationType"`
|
||||||
portainerToken = os.Getenv("PORTAINER_TOKEN")
|
GitCredentialID int64 `json:"gitCredentialID"`
|
||||||
endpointIDStr = os.Getenv("PORTAINER_ENDPOINT")
|
Password string `json:"password"`
|
||||||
stackName = os.Getenv("STACK_NAME")
|
Username string `json:"username"`
|
||||||
repoURL = os.Getenv("REPO_URL")
|
}
|
||||||
repoRef = os.Getenv("REPO_REF")
|
|
||||||
repoComposeFile = os.Getenv("REPO_COMPOSE_FILE")
|
type Option struct {
|
||||||
repoUsername = os.Getenv("REPO_USERNAME")
|
Prune bool `json:"prune"`
|
||||||
repoPassword = os.Getenv("REPO_PASSWORD")
|
}
|
||||||
tlsSkipVerify = os.Getenv("TLS_SKIP_VERIFY")
|
|
||||||
envData = os.Getenv("ENV_DATA")
|
type ResourceControl struct {
|
||||||
)
|
AccessLevel int64 `json:"AccessLevel"`
|
||||||
|
AdministratorsOnly bool `json:"AdministratorsOnly"`
|
||||||
|
ID int64 `json:"Id"`
|
||||||
|
OwnerID int64 `json:"OwnerId"`
|
||||||
|
Public bool `json:"Public"`
|
||||||
|
ResourceID string `json:"ResourceId"`
|
||||||
|
SubResourceIDS []string `json:"SubResourceIds"`
|
||||||
|
System bool `json:"System"`
|
||||||
|
TeamAccesses []TeamAccess `json:"TeamAccesses"`
|
||||||
|
Type int64 `json:"Type"`
|
||||||
|
UserAccesses []UserAccess `json:"UserAccesses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TeamAccess struct {
|
||||||
|
AccessLevel int64 `json:"AccessLevel"`
|
||||||
|
TeamID int64 `json:"TeamId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAccess struct {
|
||||||
|
AccessLevel int64 `json:"AccessLevel"`
|
||||||
|
UserID int64 `json:"UserId"`
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// Helpers
|
// HTTP helpers
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
|
|
||||||
func request(client *http.Client, method, url string, body interface{}) (*http.Response, error) {
|
func request(client *http.Client, token, method, url string, body interface{}) (*http.Response, error) {
|
||||||
var buf io.Reader
|
var buf io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
data, err := json.Marshal(body)
|
data, err := json.Marshal(body)
|
||||||
@@ -71,7 +136,7 @@ func request(client *http.Client, method, url string, body interface{}) (*http.R
|
|||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("X-API-KEY", portainerToken)
|
req.Header.Set("X-API-KEY", token)
|
||||||
if method != "GET" {
|
if method != "GET" {
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
@@ -80,129 +145,196 @@ func request(client *http.Client, method, url string, body interface{}) (*http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
// Main logic
|
// Stack deployment logic
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
|
|
||||||
func main() {
|
func Deploy(cfg Config) error {
|
||||||
// Validate required environment variables
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
if portainerURL == "" || portainerToken == "" || endpointIDStr == "" || repoURL == "" || repoRef == "" || repoComposeFile == "" || stackName == "" {
|
if cfg.TLSSkipVerify {
|
||||||
fmt.Println("❌ Missing required environment variables.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert endpointID to integer
|
|
||||||
endpointID, err := strconv.Atoi(endpointIDStr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ Invalid PORTAINER_ENDPOINT value: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure HTTP client
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 15 * time.Second,
|
|
||||||
}
|
|
||||||
if tlsSkipVerify == "true" {
|
|
||||||
client.Transport = &http.Transport{
|
client.Transport = &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Fetch existing stacks
|
var envVars []Env
|
||||||
resp, err := request(client, "GET", fmt.Sprintf("%s/stacks", portainerURL), nil)
|
if cfg.EnvData != "" {
|
||||||
if err != nil {
|
if err := json.Unmarshal([]byte(cfg.EnvData), &envVars); err != nil {
|
||||||
fmt.Printf("❌ Error fetching stacks: %v\n", err)
|
return fmt.Errorf("invalid env data: %w", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
fmt.Printf("❌ Error fetching stacks: %s\n", string(body))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var stacks []Stack
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&stacks); err != nil {
|
|
||||||
fmt.Printf("❌ Failed to parse stacks response: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Find existing stack
|
|
||||||
var existing *Stack
|
|
||||||
for _, s := range stacks {
|
|
||||||
if s.GitConfig.URL == repoURL && s.Name == stackName {
|
|
||||||
existing = &s
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Parse environment variables
|
|
||||||
var envVars []EnvVar
|
|
||||||
if envData != "" {
|
|
||||||
if err := json.Unmarshal([]byte(envData), &envVars); err != nil {
|
|
||||||
fmt.Printf("❌ Error parsing ENV_DATA: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if existing == nil {
|
|
||||||
// 4. Create new stack
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"composeFile": repoComposeFile,
|
"method": "repository",
|
||||||
"env": envVars,
|
"type": "standalone",
|
||||||
"fromAppTemplate": false,
|
"Name": cfg.StackName,
|
||||||
"name": stackName,
|
"RepositoryURL": cfg.RepoURL,
|
||||||
"repositoryAuthentication": repoUsername != "" && repoPassword != "",
|
"RepositoryReferenceName": cfg.RepoRef,
|
||||||
"repositoryUsername": repoUsername,
|
"ComposeFile": cfg.RepoComposeFile,
|
||||||
"repositoryPassword": repoPassword,
|
"AdditionalFiles": []string{},
|
||||||
"repositoryReferenceName": repoRef,
|
"RepositoryAuthenticationType": 0,
|
||||||
"repositoryURL": repoURL,
|
"RepositoryUsername": cfg.RepoUsername,
|
||||||
"tlsskipVerify": tlsSkipVerify == "true",
|
"RepositoryPassword": cfg.RepoPassword,
|
||||||
|
"Env": envVars,
|
||||||
|
"TLSSkipVerify": cfg.TLSSkipVerify,
|
||||||
|
"AutoUpdate": map[string]interface{}{
|
||||||
|
"Interval": "",
|
||||||
|
"Webhook": "",
|
||||||
|
"ForceUpdate": false,
|
||||||
|
"ForcePullImage": false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/stacks/create/standalone/repository?endpointId=%d", portainerURL, endpointID)
|
url := fmt.Sprintf("%s/stacks/create/standalone/repository?endpointId=%d", cfg.PortainerURL, cfg.EndpointID)
|
||||||
resp, err = request(client, "POST", url, payload)
|
resp, err := request(client, cfg.PortainerToken, "POST", url, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Error creating stack: %v\n", err)
|
return fmt.Errorf("create stack: %w", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
fmt.Printf("❌ Error creating stack: %s\n", string(body))
|
return fmt.Errorf("error creating stack: %s", string(body))
|
||||||
os.Exit(1)
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Stack %s deployed successfully.\n", cfg.StackName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Redeploy(cfg Config) error {
|
||||||
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
|
if cfg.TLSSkipVerify {
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var envVars []Env
|
||||||
|
if cfg.EnvData != "" {
|
||||||
|
if err := json.Unmarshal([]byte(cfg.EnvData), &envVars); err != nil {
|
||||||
|
return fmt.Errorf("invalid env data: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("✅ Stack %s deployed successfully.\n", stackName)
|
|
||||||
} else {
|
|
||||||
// 5. Redeploy existing stack
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"env": envVars,
|
"env": envVars,
|
||||||
"prune": true,
|
"prune": false,
|
||||||
"pullImage": true,
|
"PullImage": true,
|
||||||
"repositoryAuthentication": repoUsername != "" && repoPassword != "",
|
"repositoryAuthentication": true,
|
||||||
"repositoryAuthorizationType": 0,
|
"RepositoryReferenceName": "HEAD",
|
||||||
"repositoryUsername": repoUsername,
|
"repositoryPassword": cfg.RepoPassword,
|
||||||
"repositoryPassword": repoPassword,
|
"repositoryUsername": cfg.RepoUsername,
|
||||||
"repositoryReferenceName": repoRef,
|
"stackName": cfg.StackName,
|
||||||
"stackName": stackName,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/stacks/%d/git/redeploy?endpointId=%d", portainerURL, existing.ID, endpointID)
|
url := fmt.Sprintf("%s/stacks/%d/git/redeploy?endpointId=%d", cfg.PortainerURL, s.ID, cfg.EndpointID)
|
||||||
resp, err = request(client, "PUT", url, payload)
|
resp, err := request(client, cfg.PortainerToken, "PUT", url, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Error redeploying stack: %v\n", err)
|
return fmt.Errorf("redeploy stack: %w", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
fmt.Printf("❌ Error redeploying stack: %s\n", string(body))
|
return fmt.Errorf("error redeploying stack: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("♻️ Stack %s redeployed successfully.\n", cfg.StackName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findStack(cfg Config) (*Stack, error) {
|
||||||
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
|
if cfg.TLSSkipVerify {
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := request(client, cfg.PortainerToken, "GET", fmt.Sprintf("%s/stacks", cfg.PortainerURL), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetch stacks: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("failed fetching stacks: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var stacks Stacks
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&stacks); err != nil {
|
||||||
|
return nil, fmt.Errorf("decode stacks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Stack data: %+v\n", stacks)
|
||||||
|
|
||||||
|
var existing *Stack = nil
|
||||||
|
for _, s := range stacks {
|
||||||
|
if s.GitConfig.URL == cfg.RepoURL && s.Name == cfg.StackName {
|
||||||
|
existing = &s
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// CLI setup
|
||||||
|
// -------------------------------
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := Config{}
|
||||||
|
flag.StringVar(&cfg.PortainerURL, "url", "", "Portainer API base URL (e.g., https://portainer.example.com/api)")
|
||||||
|
flag.StringVar(&cfg.PortainerToken, "token", "", "Portainer API key or token")
|
||||||
|
endpointID := flag.String("endpoint", "", "Portainer endpoint ID")
|
||||||
|
flag.StringVar(&cfg.StackName, "stack", "", "Stack name")
|
||||||
|
flag.StringVar(&cfg.RepoURL, "repo-url", "", "Repository URL")
|
||||||
|
flag.StringVar(&cfg.RepoRef, "repo-ref", "main", "Git reference/branch")
|
||||||
|
flag.StringVar(&cfg.RepoComposeFile, "compose", "docker-compose.yml", "Path to Compose file in repo")
|
||||||
|
flag.StringVar(&cfg.RepoUsername, "repo-user", "", "Repository username (optional)")
|
||||||
|
flag.StringVar(&cfg.RepoPassword, "repo-pass", "", "Repository password (optional)")
|
||||||
|
flag.BoolVar(&cfg.TLSSkipVerify, "insecure", false, "Skip TLS certificate verification")
|
||||||
|
flag.StringVar(&cfg.EnvData, "env", "", "Environment variables in JSON format, e.g. '[{\"name\":\"VAR\",\"value\":\"123\"}]'")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if cfg.PortainerURL == "" || cfg.PortainerToken == "" ||
|
||||||
|
cfg.RepoURL == "" || cfg.RepoRef == "" || cfg.RepoComposeFile == "" || cfg.StackName == "" {
|
||||||
|
flag.Usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("♻️ Stack %s redeployed successfully.\n", stackName)
|
id, err := strconv.Atoi(*endpointID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Invalid endpoint ID: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
cfg.EndpointID = id
|
||||||
|
|
||||||
|
stack, err := findStack(cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "❌ %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if stack != nil {
|
||||||
|
fmt.Printf("✅ Found existing stack: %+v\n", stack)
|
||||||
|
|
||||||
|
if err := stack.Redeploy(cfg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "❌ %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Deploy(cfg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "❌ %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user