Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
name: Release

on:
push:
tags: ["v*"]
# Tag-triggered releases are temporarily paused (TX-167). The pipeline
# itself is kept intact; run it manually from the Actions tab or via
# `gh workflow run release.yml` when releases resume.
workflow_dispatch:

permissions:
contents: write
Expand Down
18 changes: 13 additions & 5 deletions cmd/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/tronprotocol/tron-deployment/internal/intent"
"github.com/tronprotocol/tron-deployment/internal/output"
"github.com/tronprotocol/tron-deployment/internal/render"
)

var validateExplain bool
Expand Down Expand Up @@ -43,13 +44,20 @@ func runValidate(cmd *cobra.Command, args []string) error {
"Refer to examples/ for valid intent files",
)
}
raw, rawErr := intent.LoadRaw(intentPath)
if rawErr != nil {
return output.NewError("VALIDATION_ERROR", output.ExitValidationError, rawErr.Error())
}
if err := render.ValidateIntentHTTPPortConflicts("", parsed, raw); err != nil {
return output.NewError("VALIDATION_ERROR", output.ExitValidationError, err.Error()).
WithSuggestions(
"Change ports.http, or move the colliding service: ports.solidity_http for solidityPort, config_overrides \"node.http.PBFTPort\" for PBFTPort",
"Run config render after correcting the conflicting HTTP port",
)
}

if validateExplain {
// Need the raw (no-defaults) form to distinguish explicit vs default.
raw, rawErr := intent.LoadRaw(intentPath)
if rawErr != nil {
return output.NewError("VALIDATION_ERROR", output.ExitValidationError, rawErr.Error())
}
// The raw (no-defaults) form distinguishes explicit vs default.
fmt.Printf("Intent %q is valid (%s, %d node(s))\n\n", parsed.Name, parsed.Network, len(parsed.Nodes))
printExplain(os.Stdout, raw, parsed)
return nil
Expand Down
4 changes: 3 additions & 1 deletion cmd/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package cmd
import (
"encoding/json"
"fmt"
"time"

"github.com/spf13/cobra"

"github.com/tronprotocol/tron-deployment/internal/output"
"github.com/tronprotocol/tron-deployment/internal/target"
)

var healthCmd = &cobra.Command{
Expand Down Expand Up @@ -39,7 +41,7 @@ func runHealth(cmd *cobra.Command, args []string) error {
}
url := fmt.Sprintf("http://127.0.0.1:%d/wallet/getnowblock", httpPort)

out, err := nc.Target.Exec(cmd.Context(), "curl", "-s", "--max-time", "5", url)
out, err := target.Get(cmd.Context(), nc.Target, url, 5*time.Second)
if err != nil {
result := map[string]any{
"name": name,
Expand Down
6 changes: 4 additions & 2 deletions cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/tronprotocol/tron-deployment/internal/apply"
"github.com/tronprotocol/tron-deployment/internal/output"
"github.com/tronprotocol/tron-deployment/internal/state"
"github.com/tronprotocol/tron-deployment/internal/target"
)

func stdoutWriter() io.Writer { return os.Stdout }
Expand Down Expand Up @@ -166,11 +167,12 @@ func buildManifest(ctx context.Context, nodes []state.ManagedNode) map[string]an

func manifestForNode(ctx context.Context, n *state.ManagedNode) map[string]any {
endpoints := map[string]string{}
host := target.EndpointHost(n.Target.Type, n.Target.Host)
if n.HTTPPort != 0 {
endpoints["http"] = fmt.Sprintf("http://127.0.0.1:%d", n.HTTPPort)
endpoints["http"] = fmt.Sprintf("http://%s:%d", host, n.HTTPPort)
}
if n.GRPCPort != 0 {
endpoints["grpc"] = fmt.Sprintf("127.0.0.1:%d", n.GRPCPort)
endpoints["grpc"] = fmt.Sprintf("%s:%d", host, n.GRPCPort)
}

entry := map[string]any{
Expand Down
4 changes: 2 additions & 2 deletions cmd/network/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ func runAdd(cmd *cobra.Command, args []string) error {
"network": addNetworkName,
"added": nodeName,
"endpoints": map[string]string{
"http": fmt.Sprintf("http://127.0.0.1:%d", node.Ports.HTTP),
"grpc": fmt.Sprintf("127.0.0.1:%d", node.Ports.GRPC),
"http": fmt.Sprintf("http://%s:%d", target.EndpointHost(parsed.Target.Type, parsed.Target.Host), node.Ports.HTTP),
"grpc": fmt.Sprintf("%s:%d", target.EndpointHost(parsed.Target.Type, parsed.Target.Host), node.Ports.GRPC),
},
}
output.WriteJSON(os.Stdout, result)
Expand Down
4 changes: 2 additions & 2 deletions cmd/network/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,10 @@ func deployNetworkMonitoring(ctx context.Context, tgt target.Target, workDir str
}

urls := map[string]string{
"grafana_url": fmt.Sprintf("http://127.0.0.1:%d", parsed.Monitoring.Grafana.Port),
"grafana_url": fmt.Sprintf("http://%s:%d", target.EndpointHost(parsed.Target.Type, parsed.Target.Host), parsed.Monitoring.Grafana.Port),
}
if parsed.Monitoring.Prometheus.Port > 0 {
urls["prometheus_url"] = fmt.Sprintf("http://127.0.0.1:%d", parsed.Monitoring.Prometheus.Port)
urls["prometheus_url"] = fmt.Sprintf("http://%s:%d", target.EndpointHost(parsed.Target.Type, parsed.Target.Host), parsed.Monitoring.Prometheus.Port)
}
return monitoringResult{
urls: urls,
Expand Down
37 changes: 33 additions & 4 deletions cmd/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"net"
"os"
"strings"
"time"
Expand Down Expand Up @@ -74,6 +73,7 @@ func runPreflight(cmd *cobra.Command, args []string) error {

// Memory check
checks = append(checks, checkMemory(cmd, tgt, parsed))
checks = append(checks, checkMemoryRecommended(parsed)...)

// Port check
for _, node := range parsed.Nodes {
Expand Down Expand Up @@ -197,12 +197,42 @@ func checkMemory(cmd *cobra.Command, tgt target.Target, parsed *intent.Intent) c
Message: fmt.Sprintf("%dGB total", memGB)}
}

// checkMemoryRecommended warns about container memory below java-tron's
// official startup recommendation. This is an intent-side check: it does not
// inspect the target's available memory or alter the existing memory check.
func checkMemoryRecommended(parsed *intent.Intent) []checkResult {
var checks []checkResult
for i, node := range parsed.Nodes {
memoryGB := render.ParseMemoryGB(node.Resources.Memory)
if memoryGB <= 0 || memoryGB >= 8 {
continue
}
nodeName := parsed.Name
if len(parsed.Nodes) > 1 {
nodeName = fmt.Sprintf("%s-node%d", parsed.Name, i)
}
checks = append(checks, checkResult{
Name: "memory-recommended",
Status: "warning",
Message: fmt.Sprintf("node '%s' requests %s container memory, below java-tron's official minimum 8192MB (start.sh ALLOW_MIN_MEMORY); small containers risk JVM startup failure", nodeName, node.Resources.Memory),
})
}
if len(checks) == 0 {
return []checkResult{{
Name: "memory-recommended",
Status: "pass",
Message: "all nodes meet java-tron's official 8192MB minimum",
}}
}
return checks
}

// checkPorts probes every well-known port the intent will expose.
// Earlier this shelled out to `ss -tlnp` which doesn't exist on macOS,
// so the check silently reported every port as available. Use net.Dial
// instead — same behaviour as the diagnose port_listening checker, no
// runtime dependency.
func checkPorts(_ *cobra.Command, _ target.Target, node *intent.NodeSpec) []checkResult {
func checkPorts(cmd *cobra.Command, tgt target.Target, node *intent.NodeSpec) []checkResult {
ports := []struct {
name string
port int
Expand All @@ -212,13 +242,12 @@ func checkPorts(_ *cobra.Command, _ target.Target, node *intent.NodeSpec) []chec
{"p2p", node.Ports.P2P},
}

dialer := net.Dialer{Timeout: 1500 * time.Millisecond}
var results []checkResult
for _, p := range ports {
if p.port == 0 {
continue
}
conn, err := dialer.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", p.port))
conn, err := target.DialContext(cmd.Context(), tgt, "tcp", fmt.Sprintf("127.0.0.1:%d", p.port), 1500*time.Millisecond)
if err == nil {
_ = conn.Close()
results = append(results, checkResult{
Expand Down
70 changes: 70 additions & 0 deletions cmd/preflight_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd

import (
"strings"
"testing"

"github.com/tronprotocol/tron-deployment/internal/intent"
)

func TestCheckMemoryRecommended(t *testing.T) {
tests := []struct {
name string
nodes []intent.NodeSpec
warnings int
status string
messageHas []string
}{
{
name: "4GB warns",
nodes: []intent.NodeSpec{{Type: "fullnode", Resources: intent.Resources{Memory: "4GB"}}},
warnings: 1, messageHas: []string{"node 'test'", "8192MB"},
},
{
name: "8GB passes",
nodes: []intent.NodeSpec{{Type: "fullnode", Resources: intent.Resources{Memory: "8GB"}}},
status: "pass",
},
{
name: "16GB passes",
nodes: []intent.NodeSpec{{Type: "fullnode", Resources: intent.Resources{Memory: "16GB"}}},
status: "pass",
},
{
name: "mixed memory warns once",
nodes: []intent.NodeSpec{
{Type: "fullnode", Resources: intent.Resources{Memory: "2GB"}},
{Type: "fullnode", Resources: intent.Resources{Memory: "16GB"}},
},
warnings: 1,
},
{
name: "empty memory uses default",
nodes: []intent.NodeSpec{{Type: "fullnode"}},
status: "pass",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
checks := checkMemoryRecommended(&intent.Intent{Name: "test", Nodes: tt.nodes})
gotWarnings := 0
for _, check := range checks {
if check.Status == "warning" {
gotWarnings++
}
for _, part := range tt.messageHas {
if !strings.Contains(check.Message, part) {
t.Errorf("message %q missing %q", check.Message, part)
}
}
}
if gotWarnings != tt.warnings {
t.Errorf("warning count = %d, want %d", gotWarnings, tt.warnings)
}
if tt.status != "" && (len(checks) != 1 || checks[0].Status != tt.status) {
t.Errorf("checks = %+v, want one %s check", checks, tt.status)
}
})
}
}
4 changes: 2 additions & 2 deletions cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ func runStatus(cmd *cobra.Command, args []string) error {
"healthy": false,
"logs": apply.LogsDescriptor(node),
"api_endpoints": map[string]any{
"http": fmt.Sprintf("http://127.0.0.1:%d", effectivePort(node.HTTPPort, 8090)),
"grpc": fmt.Sprintf("127.0.0.1:%d", effectivePort(node.GRPCPort, 50051)),
"http": fmt.Sprintf("http://%s:%d", target.EndpointHost(node.Target.Type, node.Target.Host), effectivePort(node.HTTPPort, 8090)),
"grpc": fmt.Sprintf("%s:%d", target.EndpointHost(node.Target.Type, node.Target.Host), effectivePort(node.GRPCPort, 50051)),
},
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/tronprotocol/tron-deployment/internal/intent"
"github.com/tronprotocol/tron-deployment/internal/output"
"github.com/tronprotocol/tron-deployment/internal/state"
"github.com/tronprotocol/tron-deployment/internal/target"
)

var (
Expand Down Expand Up @@ -57,7 +58,7 @@ func runVerify(cmd *cobra.Command, args []string) error {
for time.Now().Before(deadline) {
attempt++
url := fmt.Sprintf("http://127.0.0.1:%d/wallet/getnowblock", httpPort)
out, err := tgt.Exec(cmd.Context(), "curl", "-s", "--max-time", "5", url)
out, err := target.Get(cmd.Context(), tgt, url, 5*time.Second)
if err == nil {
var block struct {
BlockHeader struct {
Expand Down
18 changes: 12 additions & 6 deletions cmd/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"net"
"os"
"strings"
"time"
Expand Down Expand Up @@ -184,9 +183,8 @@ func expandHTTPURL(raw string, port int) string {
return strings.ReplaceAll(raw, "{http}", fmt.Sprintf("http://127.0.0.1:%d", port))
}

func probeTCP(ctx context.Context, _ target.Target, port int) error {
d := net.Dialer{Timeout: 2 * time.Second}
conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("127.0.0.1:%d", port))
func probeTCP(ctx context.Context, tgt target.Target, port int) error {
conn, err := target.DialContext(ctx, tgt, "tcp", fmt.Sprintf("127.0.0.1:%d", port), 2*time.Second)
if err != nil {
return err
}
Expand All @@ -212,8 +210,16 @@ func httpProbeURL(raw string) (string, error) {
}

func probeHTTP(ctx context.Context, nc *nodeContext, url string) error {
args := []string{"-fsS", "--max-time", "5", url}
out, err := nc.runtimeExec(ctx, "curl", args...)
var out []byte
var err error
probeCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if nc.Node.Runtime == "jar" {
out, err = target.Get(probeCtx, nc.Target, url, 5*time.Second)
} else {
args := []string{"-fsS", "--max-time", "5", url}
out, err = nc.runtimeExec(ctx, "curl", args...)
}
if err != nil {
return fmt.Errorf("curl: %w", err)
}
Expand Down
5 changes: 4 additions & 1 deletion examples/private-network.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ nodes:
resources:
memory: 8GB
ports:
http: 8091
# Host http must dodge the witness's 8090 — but NOT 8091/8092: those are
# java-tron's in-container solidityPort/PBFTPort defaults, and the 1:1
# port mapping would collide with them (trond rejects such intents).
http: 8095
grpc: 50052
jsonrpc: 8545
p2p: 18889
Expand Down
17 changes: 9 additions & 8 deletions internal/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,8 @@ func Apply(ctx context.Context, opts Options) (*Result, error) {
Network: opts.Intent.Network,
IsPrivate: intent.IsPrivate(opts.Intent.Network),
Endpoints: map[string]string{
"http": fmt.Sprintf("http://127.0.0.1:%d", node.Ports.HTTP),
"grpc": fmt.Sprintf("127.0.0.1:%d", node.Ports.GRPC),
"http": fmt.Sprintf("http://%s:%d", target.EndpointHost(opts.Intent.Target.Type, opts.Intent.Target.Host), node.Ports.HTTP),
"grpc": fmt.Sprintf("%s:%d", target.EndpointHost(opts.Intent.Target.Type, opts.Intent.Target.Host), node.Ports.GRPC),
},
DurationMs: deployedMs,
Build: buildSummary,
Expand Down Expand Up @@ -562,6 +562,7 @@ func noChangeResult(opts Options, buildSummary *BuildSummary, start time.Time) *
}

ports := opts.Intent.Nodes[0].Ports
host := target.EndpointHost(opts.Intent.Target.Type, opts.Intent.Target.Host)
return &Result{
Name: opts.Intent.Name,
Outcome: "no_change",
Expand All @@ -572,24 +573,24 @@ func noChangeResult(opts Options, buildSummary *BuildSummary, start time.Time) *
Network: opts.Intent.Network,
IsPrivate: intent.IsPrivate(opts.Intent.Network),
Endpoints: map[string]string{
"http": fmt.Sprintf("http://127.0.0.1:%d", ports.HTTP),
"grpc": fmt.Sprintf("127.0.0.1:%d", ports.GRPC),
"http": fmt.Sprintf("http://%s:%d", host, ports.HTTP),
"grpc": fmt.Sprintf("%s:%d", host, ports.GRPC),
},
DurationMs: time.Since(start).Milliseconds(),
Build: buildSummary,
MonitoringEndpoints: monitoringEndpointsFromExisting(opts.Existing),
MonitoringEndpoints: monitoringEndpointsFromExisting(opts.Existing, host),
}
}

// monitoringEndpointsFromExisting returns monitoring URLs from a managed
// node's saved state, if monitoring was deployed for it.
func monitoringEndpointsFromExisting(existing *state.ManagedNode) map[string]string {
func monitoringEndpointsFromExisting(existing *state.ManagedNode, host string) map[string]string {
if existing == nil || existing.Monitoring == nil || !existing.Monitoring.Enabled {
return nil
}
return map[string]string{
"prometheus_url": fmt.Sprintf("http://127.0.0.1:%d", existing.Monitoring.PrometheusPort),
"grafana_url": fmt.Sprintf("http://127.0.0.1:%d", existing.Monitoring.GrafanaPort),
"prometheus_url": fmt.Sprintf("http://%s:%d", host, existing.Monitoring.PrometheusPort),
"grafana_url": fmt.Sprintf("http://%s:%d", host, existing.Monitoring.GrafanaPort),
}
}

Expand Down
Loading
Loading