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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ id_ecdsa*
.DS_Store
.idea/
.vscode/
.devcontainer/
scratch/

# Local scratch documentation & OpenAPI specifications
Expand Down
3 changes: 2 additions & 1 deletion pkg/bigip/failover.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ func DownloadFileWithFailover(
strings.HasSuffix(cleanRemote, ".iso") ||
strings.HasSuffix(cleanRemote, ".qkview")

tryRestFirst := isRestFriendly || opts.ClientCertPath != "" || (opts.IdentityFile == "" && (opts.Password != "" || os.Getenv("BIGIP_PASSWORD") != "" || os.Getenv("BIGIQ_PASSWORD") != ""))
hasRestAuth := opts.Password != "" || len(opts.PasswordBytes) > 0 || opts.ClientCertPath != "" || os.Getenv("BIGIP_PASSWORD") != "" || os.Getenv("BIGIQ_PASSWORD") != ""
tryRestFirst := hasRestAuth && (isRestFriendly || opts.IdentityFile == "")

if tryRestFirst {
savedPath, restErr := downloadViaIControl(ctx, host, remotePath, localDest, opts, progressCb)
Expand Down
34 changes: 30 additions & 4 deletions pkg/bigip/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,42 @@ func (s *SystemInfo) PreferredSerialNumber() string {
return s.RegistrationKey
}

// IsBIGIQ returns true if the appliance is a BIG-IQ Centralized Management system.
func (s *SystemInfo) IsBIGIQ() bool {
if s == nil {
return false
}
if strings.Contains(strings.ToUpper(s.Product), "BIG-IQ") {
return true
}
if lvl, ok := s.ProvisionedModules["biq"]; ok && lvl != "" && lvl != "none" {
return true
}
return false
}

// PopulateCaseAttributes computes and stores standard attributes for F5 Support Case creation.
func (s *SystemInfo) PopulateCaseAttributes() {
if s == nil {
return
}
isBigIQ := s.IsBIGIQ()
family := "BIG-IP"
if isBigIQ {
family = "BIG-IQ"
}

attrs := map[string]string{
"productFamily": "BIG-IP",
"productFamily": family,
"serialNumber": s.PreferredSerialNumber(),
"version": s.Version,
"hostName": s.Hostname,
}

product := "BIG-IP"
if len(s.ProvisionedModules) > 0 {
if isBigIQ {
product = "BIG-IQ Centralized Management"
} else if len(s.ProvisionedModules) > 0 {
var active []string
for mod, level := range s.ProvisionedModules {
if level != "none" && level != "" && !isInternalModule(mod) {
Expand Down Expand Up @@ -133,8 +155,12 @@ func (s *SystemInfo) Summary() string {
if s.Platform != "" {
platDisplay := s.Platform
if rec, ok := LookupPlatform(s.Platform); ok && rec.Model != "" {
if !strings.Contains(s.Platform, rec.Model) && s.Platform != rec.Model {
platDisplay = fmt.Sprintf("%s (%s)", s.Platform, rec.Model)
model := rec.Model
if s.IsBIGIQ() && strings.HasPrefix(model, "BIG-IP Virtual Edition") {
model = strings.Replace(model, "BIG-IP Virtual Edition", "BIG-IQ Virtual Edition", 1)
}
if !strings.Contains(s.Platform, model) && s.Platform != model {
platDisplay = fmt.Sprintf("%s (%s)", s.Platform, model)
}
}
b.WriteString(fmt.Sprintf(" Platform: %s\n", platDisplay))
Expand Down
26 changes: 26 additions & 0 deletions pkg/bigip/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,4 +551,30 @@ func TestSystemInfo_SummaryPlatformEnrichment(t *testing.T) {
if !strings.Contains(sum3, "Platform: BIG-IP Virtual Edition") || strings.Contains(sum3, "BIG-IP Virtual Edition (BIG-IP Virtual Edition)") {
t.Errorf("Summary() duplicate enrichment for BIG-IP Virtual Edition:\n%s", sum3)
}

// Case 4: BIG-IQ 8.4.2.3 on Z100 Virtual Edition
info4 := &SystemInfo{
Product: "BIG-IQ",
Version: "8.4.2.3",
Build: "0.0.8",
Edition: "Final",
Platform: "Z100",
ChassisSerialNumber: "test-bigiq-serial-001",
Hostname: "bigiq-ve.ec2.internal",
ProvisionedModules: map[string]string{"biq": "nominal"},
}
info4.PopulateCaseAttributes()
sum4 := info4.Summary()
if info4.CaseAttributes["productFamily"] != "BIG-IQ" {
t.Errorf("CaseAttributes[productFamily] = %q; want BIG-IQ", info4.CaseAttributes["productFamily"])
}
if info4.CaseAttributes["product"] != "BIG-IQ Centralized Management" {
t.Errorf("CaseAttributes[product] = %q; want BIG-IQ Centralized Management", info4.CaseAttributes["product"])
}
if !strings.Contains(sum4, "Platform: Z100 (BIG-IQ Virtual Edition)") {
t.Errorf("Summary() did not enrich Z100 as BIG-IQ Virtual Edition:\n%s", sum4)
}
if !strings.Contains(sum4, "Software (8.4.2.3 ): Supported") || strings.Contains(sum4, "[WARNING]") {
t.Errorf("Summary() did not mark BIG-IQ 8.4.2.3 as Supported:\n%s", sum4)
}
}
76 changes: 75 additions & 1 deletion pkg/bigip/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,62 @@ func EvaluatePlatformLifecycle(platform string, asOf time.Time) HardwareLifecycl
return res
}

// EvaluateBIGIQLifecycle checks a BIG-IQ Centralized Management software version
// against F5 Software Lifecycle Policy K5903.
func EvaluateBIGIQLifecycle(version string, asOf time.Time) SoftwareLifecycle {
cleanVer := strings.TrimSpace(version)
res := SoftwareLifecycle{
Version: cleanVer,
NoticeURL: URLPolicyTMOS,
Status: StatusUnknown,
}

if cleanVer == "" {
return res
}

major, minor, _, ok := ParseTMOSVersion(cleanVer)
if !ok {
return res
}

res.Branch = fmt.Sprintf("%d.%d", major, minor)

switch {
case major < 8 || (major == 8 && minor < 3):
res.EoSD = "2024-12-31"
res.EoTS = "2024-12-31"
res.Status = StatusEOL
res.IsEOL = true
res.Warning = fmt.Sprintf("BIG-IQ version %s reached End of Life (EoSD/EoTS) on %s (K5903).", cleanVer, res.EoSD)
res.Recommendation = "Upgrade to an actively supported BIG-IQ 8.4.x release to receive software fixes and support."
return res

case major == 8 && minor == 3:
eosdDate := time.Date(2025, time.December, 31, 23, 59, 59, 0, time.UTC)
res.EoSD = eosdDate.Format("2006-01-02")
res.EoTS = res.EoSD
if asOf.After(eosdDate) {
res.Status = StatusEOL
res.IsEOL = true
res.Warning = fmt.Sprintf("BIG-IQ version %s reached End of Life (EoSD/EoTS) on %s (K5903).", cleanVer, res.EoSD)
res.Recommendation = "Upgrade to an actively supported BIG-IQ 8.4.x release to receive software fixes and support."
} else {
res.Status = StatusSupported
res.IsEOL = false
res.Recommendation = fmt.Sprintf("Standard support active until %s.", res.EoSD)
}
return res

default:
// BIG-IQ 8.4.x and newer releases
res.Status = StatusSupported
res.IsEOL = false
res.Recommendation = "Active BIG-IQ release stream (K5903)."
return res
}
}

// EvaluateLifecycle evaluates both software and hardware lifecycle statuses.
func EvaluateLifecycle(version, platform string, asOf time.Time) LifecycleInfo {
sw := EvaluateTMOSLifecycle(version, asOf)
Expand Down Expand Up @@ -612,7 +668,25 @@ func (s *SystemInfo) EvaluateLifecycle(asOf time.Time) {
if plat == "" && s.CaseAttributes != nil && s.CaseAttributes["cloudProvider"] != "" {
plat = s.CaseAttributes["cloudProvider"] + " Virtual Edition"
}
lc := EvaluateLifecycle(s.Version, plat, asOf)
var lc LifecycleInfo
if s.IsBIGIQ() {
sw := EvaluateBIGIQLifecycle(s.Version, asOf)
hw := EvaluatePlatformLifecycle(plat, asOf)
var warnings []string
if sw.Warning != "" {
warnings = append(warnings, sw.Warning)
}
if hw.Warning != "" {
warnings = append(warnings, hw.Warning)
}
lc = LifecycleInfo{
Software: sw,
Hardware: hw,
Warnings: warnings,
}
} else {
lc = EvaluateLifecycle(s.Version, plat, asOf)
}
s.Lifecycle = &lc
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/bigip/scp.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,16 @@ func SCPUpload(ctx context.Context, localPath string, destDir string, opts SCPOp
} else {
destDir = "/var/tmp"
}
} else if path.Base(destDir) == baseFilename || path.Ext(destDir) != "" {
baseFilename = path.Base(destDir)
destDir = path.Dir(destDir)
}
if err := ValidateRemoteAppliancePath(destDir); err != nil {
return "", fmt.Errorf("invalid destination directory: %w", err)
}
if err := ValidateRemoteAppliancePath(path.Join(destDir, baseFilename)); err != nil {
return "", fmt.Errorf("invalid destination file path: %w", err)
}

host, port := CleanSSHHost(opts.Host)
if opts.Port > 0 {
Expand Down
Loading