diff --git a/bigip.go b/bigip.go index 37fd906..b5fe5cc 100644 --- a/bigip.go +++ b/bigip.go @@ -153,29 +153,24 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { Timeout struct { Timeout int64 } + RefreshToken interface{} `json:"refreshToken,omitempty"` } type timeoutReq struct { Timeout int64 `json:"timeout"` } - - // type timeoutResp struct { - // Timeout struct { - // Timeout int64 - // } - // } - + if bigipConfig.LoginReference == "" { + bigipConfig.LoginReference = "tmos" + } auth := authReq{ bigipConfig.Username, bigipConfig.Password, bigipConfig.LoginReference, } - marshalJSONauth, err := json.Marshal(auth) if err != nil { return } - req := &APIRequest{ Method: "post", URL: "mgmt/shared/authn/login", @@ -227,7 +222,7 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { b.Token = aresp.Token.Token //Once we have obtained a token, we should actually apply the configured timeout to it - if time.Duration(aresp.Timeout.Timeout)*time.Second != bigipConfig.ConfigOptions.TokenTimeout { // The inital value is the max timespan + if aresp.RefreshToken == nil && time.Duration(aresp.Timeout.Timeout)*time.Second != bigipConfig.ConfigOptions.TokenTimeout { // The inital value is the max timespan timeout := timeoutReq{ int64(bigipConfig.ConfigOptions.TokenTimeout.Seconds()), } @@ -617,6 +612,10 @@ func (b *BigIP) getForEntity(e interface{}, path ...string) (error, bool) { } resp, err := b.APICall(req) + // add log for debugging + // If we get an error, we need to check if it is a 404 error. + // check error code and response body + if err != nil { var reqError RequestError json.Unmarshal(resp, &reqError) diff --git a/bigiq_functional_test.go b/bigiq_functional_test.go new file mode 100644 index 0000000..02587dd --- /dev/null +++ b/bigiq_functional_test.go @@ -0,0 +1,34 @@ +package bigip + +import ( + "os" + "testing" +) + +func TestGetManagedDevicesFunc(t *testing.T) { + host := os.Getenv("BIGIQ_HOST") + user := os.Getenv("BIGIQ_USER") + pass := os.Getenv("BIGIQ_PASSWORD") + if host == "" || user == "" || pass == "" { + t.Skip("Environment variables BIGIQ_HOST, BIGIQ_USER, BIGIQ_PASSWORD must be set") + } + b, err := NewTokenSession(&Config{ + Address: host, + Username: user, + Password: pass, + CertVerifyDisable: true, // Disable cert verification for testing + }) + if err != nil { + t.Fatalf("NewTokenSession failed: %v", err) + } + devices, err := b.GetManagedDevices() + if err != nil { + t.Fatalf("GetManagedDevices failed: %v", err) + } + if devices == nil || len(devices.DevicesInfo) == 0 { + t.Errorf("No managed devices found") + } + for _, d := range devices.DevicesInfo { + t.Logf("Managed Device: Address=%s, Hostname=%s, UUID=%s", d.Address, d.Hostname, d.UUID) + } +} diff --git a/go.mod b/go.mod index 508d569..f064edd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/f5devcentral/go-bigip -go 1.20 +go 1.22 require github.com/stretchr/testify v1.2.1 diff --git a/ltm.go b/ltm.go index 6d9d2f0..f6948b5 100644 --- a/ltm.go +++ b/ltm.go @@ -1623,8 +1623,8 @@ type Originsrecords struct { } type Originsrecord struct { - Name string `json:"name"` - app_service string `json:"appService,omitempty"` + Name string `json:"name"` + AppService string `json:"appService,omitempty"` } func (p *Snat) MarshalJSON() ([]byte, error) { @@ -4259,17 +4259,7 @@ func (b *BigIP) AddRequestLogProfile(config *RequestLogProfile) error { return b.post(config, uriLtm, uriProfile, uriRequestLog) } -// DeleteRequestLogProfile removes a Request Log profile. -func (b *BigIP) DeleteRequestLogProfile(name string) error { - return b.delete(uriLtm, uriProfile, uriRequestLog, name) -} - -// ModifyRequestLogProfile allows you to change any attribute of a RequestLog profile. -// Fields that can be modified are referenced in the RequestLogProfile struct. -func (b *BigIP) ModifyRequestLogProfile(name string, config *RequestLogProfile) error { - return b.patch(config, uriLtm, uriProfile, uriRequestLog, name) -} - +// GetRequestLogProfile gets a request log profile by name. Returns nil if the request log profile does not exist func (b *BigIP) GetRequestLogProfile(name string) (*RequestLogProfile, error) { var requestLogProfile RequestLogProfile err, ok := b.getForEntity(&requestLogProfile, uriLtm, uriProfile, uriRequestLog, name) @@ -4284,6 +4274,17 @@ func (b *BigIP) GetRequestLogProfile(name string) (*RequestLogProfile, error) { return &requestLogProfile, nil } +// DeleteRequestLogProfile removes a Request Log profile. +func (b *BigIP) DeleteRequestLogProfile(name string) error { + return b.delete(uriLtm, uriProfile, uriRequestLog, name) +} + +// ModifyRequestLogProfile allows you to change any attribute of a RequestLog profile. +// Fields that can be modified are referenced in the RequestLogProfile struct. +func (b *BigIP) ModifyRequestLogProfile(name string, config *RequestLogProfile) error { + return b.patch(config, uriLtm, uriProfile, uriRequestLog, name) +} + type BotDefenseProfile struct { Name string `json:"name,omitempty"` Partition string `json:"partition,omitempty"` diff --git a/ltm_functional_test.go b/ltm_functional_test.go new file mode 100644 index 0000000..871c864 --- /dev/null +++ b/ltm_functional_test.go @@ -0,0 +1,76 @@ +package bigip + +import ( + "os" + "testing" +) + +// Functional test for IRules() using a real or integration environment +func TestFunctionalIRules(t *testing.T) { + address := os.Getenv("BIGIP_HOST") + username := os.Getenv("BIGIP_USER") + password := os.Getenv("BIGIP_PASSWORD") + t.Logf("BIGIP_HOST=%s, BIGIP_USER=%s", address, username) // Debug log for env vars + if address == "" || username == "" || password == "" { + t.Skip("BIGIP environment variables not set") + } + cfg := &Config{ + Address: address, + Username: username, + Password: password, + CertVerifyDisable: true, // Disable certificate validation for testing + } + t.Logf("Config: %+v", cfg) // Debug log for config + client, err := NewTokenSession(cfg) + if err != nil { + t.Fatalf("NewTokenSession error: %v", err) + } + + rules, err := client.IRules() + if err != nil { + t.Fatalf("IRules() error: %v", err) + } + if rules == nil || len(rules.IRules) == 0 { + t.Errorf("No iRules returned, got: %+v", rules) + } else { + for _, rule := range rules.IRules { + t.Logf("iRule: %+v", rule) + } + } +} + +// Functional test to get a specific iRule by name +// Functional test to test the IRule() method for fetching a specific iRule by name +func TestGetSpecificIRule(t *testing.T) { + address := os.Getenv("BIGIP_HOST") + username := os.Getenv("BIGIP_USER") + password := os.Getenv("BIGIP_PASSWORD") + + iruleName := "default_f5_healt" + t.Logf("BIGIP_HOST=%s, BIGIP_USER=%s, BIGIP_IRULE_NAME=%s", address, username, iruleName) + if address == "" || username == "" || password == "" || iruleName == "" { + t.Skip("BIGIP environment variables not set or iRule name missing") + } + cfg := &Config{ + Address: address, + Username: username, + Password: password, + CertVerifyDisable: true, + } + client, err := NewTokenSession(cfg) + if err != nil { + t.Fatalf("NewTokenSession error: %v", err) + } + + irule, err := client.IRule(iruleName) + if err != nil { + t.Fatalf("IRule error: %v", err) + } + if irule == nil { + t.Errorf("iRule '%s' not found", iruleName) + } else { + t.Logf("Found iRule '%s': %+v", iruleName, irule) + } +} + +// Add more functional tests for other LTM features as needed diff --git a/ltm_irules_test.go b/ltm_irules_test.go new file mode 100644 index 0000000..e69de29 diff --git a/net_test.go b/net_test.go index 6970e40..01b3dbf 100644 --- a/net_test.go +++ b/net_test.go @@ -1,13 +1,13 @@ package bigip import ( + "io" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "io/ioutil" ) type NetTestSuite struct { @@ -21,7 +21,7 @@ type NetTestSuite struct { func (s *NetTestSuite) SetupSuite() { s.Server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, _ := ioutil.ReadAll(r.Body) + body, _ := io.ReadAll(r.Body) s.LastRequestBody = string(body) s.LastRequest = r if s.ResponseFunc != nil { @@ -29,7 +29,7 @@ func (s *NetTestSuite) SetupSuite() { } })) - s.Client = NewSession(s.Server.URL, "", "", "", nil) + s.Client = NewSession(&Config{Address: s.Server.URL}) } func (s *NetTestSuite) TearDownSuite() { @@ -131,7 +131,8 @@ func (s *NetTestSuite) TestSelfIPs() { } func (s *NetTestSuite) TestCreateSelfIP() { - err := s.Client.CreateSelfIP("0.0.0.0", "0.0.0.0/20", "vlan") + ip := &SelfIP{Name: "0.0.0.0", Address: "0.0.0.0/20", Vlan: "vlan"} + err := s.Client.CreateSelfIP(ip) assert.Nil(s.T(), err) assertRestCall(s, "POST", "/mgmt/tm/net/self", `{"name":"0.0.0.0","address":"0.0.0.0/20", "vlan":"vlan"}`) @@ -243,7 +244,9 @@ func (s *NetTestSuite) TestVlans() { } func (s *NetTestSuite) TestCreateVLan() { - err := s.Client.CreateVlan("name", 1) + vlan := &Vlan{Name: "name", Tag: 1} + // SFlow field uses the zero value (empty struct) + err := s.Client.CreateVlan(vlan) assert.Nil(s.T(), err) assertRestCall(s, "POST", "/mgmt/tm/net/vlan", `{"name":"name", "tag":1, "sflow":{}}`) @@ -295,7 +298,8 @@ func (s *NetTestSuite) TestRoutes() { } func (s *NetTestSuite) TestCreateRoute() { - err := s.Client.CreateRoute("default_route", "default", "0.0.0.0") + route := &Route{Name: "default_route", Network: "default", Gateway: "0.0.0.0"} + err := s.Client.CreateRoute(route) assert.Nil(s.T(), err) assertRestCall(s, "POST", "/mgmt/tm/net/route", `{"name":"default_route", "network":"default", "gw":"0.0.0.0"}`)