diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 1e86b2a..41645d3 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -26,8 +26,11 @@ jobs: with: dotnet-version: '8.0.x' - - name: Restore dependencies - run: dotnet restore + - name: Restore class library + run: dotnet restore classlib/classlib.csproj + + - name: Restore test project + run: dotnet restore classlib.tests/classlib.tests.csproj - name: Build class library run: dotnet build classlib/classlib.csproj --configuration Release --no-restore @@ -39,7 +42,7 @@ jobs: run: dotnet test classlib.tests/classlib.tests.csproj --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage" - name: Upload test results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 if: always() with: name: test-results @@ -86,7 +89,7 @@ jobs: cp -r images output/ps.ipam/ - name: Upload module artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: ps.ipam-module path: output/ps.ipam/ @@ -99,7 +102,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') steps: - name: Download module artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v3 with: name: ps.ipam-module path: ps.ipam diff --git a/classlib.tests/Cmdlets/GetAddressCmdletTests.cs b/classlib.tests/Cmdlets/GetAddressCmdletTests.cs index 8ca88e5..273a575 100644 --- a/classlib.tests/Cmdlets/GetAddressCmdletTests.cs +++ b/classlib.tests/Cmdlets/GetAddressCmdletTests.cs @@ -109,6 +109,13 @@ public class GetAddressCmdletTests : IDisposable cmdlet.SubnetCIDR.Should().Be("192.168.1.0/24"); } + [Fact] + public void GetAddressCmdlet_InheritsFromBaseCmdlet() + { + var cmdlet = new GetAddressCmdlet(); + cmdlet.Should().BeAssignableTo(); + } + // Test the underlying RequestHelper functionality that the cmdlet uses [Fact] @@ -129,8 +136,8 @@ public class GetAddressCmdletTests : IDisposable // Act var result = await RequestHelper.InvokeRequest( "GET", - controllers.addresses, - types.Address, + ApiController.Addresses, + ModelType.Address, null, null, new[] { "1" } @@ -155,8 +162,8 @@ public class GetAddressCmdletTests : IDisposable // Act await RequestHelper.InvokeRequest( "GET", - controllers.addresses, - types.Address, + ApiController.Addresses, + ModelType.Address, null, null, new[] { "42" } @@ -177,8 +184,8 @@ public class GetAddressCmdletTests : IDisposable // Act await RequestHelper.InvokeRequest( "GET", - controllers.addresses, - types.Address, + ApiController.Addresses, + ModelType.Address, null, null, new[] { "search", "192.168.1.50" } @@ -199,8 +206,8 @@ public class GetAddressCmdletTests : IDisposable // Act await RequestHelper.InvokeRequest( "GET", - controllers.addresses, - types.Address, + ApiController.Addresses, + ModelType.Address, null, null, new[] { "search_hostname", "myserver.example.com" } @@ -221,8 +228,8 @@ public class GetAddressCmdletTests : IDisposable // Act await RequestHelper.InvokeRequest( "GET", - controllers.subnets, - types.Address, + ApiController.Subnets, + ModelType.Address, null, null, new[] { "10", "addresses" } @@ -243,8 +250,8 @@ public class GetAddressCmdletTests : IDisposable // Act await RequestHelper.InvokeRequest( "GET", - controllers.addresses, - types.Address, + ApiController.Addresses, + ModelType.Address, null, null, new[] { "tags", "2", "addresses" } @@ -269,8 +276,8 @@ public class GetAddressCmdletTests : IDisposable // Act var result = await RequestHelper.InvokeRequest( "GET", - controllers.subnets, - types.Address, + ApiController.Subnets, + ModelType.Address, null, null, new[] { "10", "addresses" } @@ -294,8 +301,8 @@ public class GetAddressCmdletTests : IDisposable // Act var result = await RequestHelper.InvokeRequest( "GET", - controllers.addresses, - types.Address, + ApiController.Addresses, + ModelType.Address, null, null, new[] { "999" } @@ -313,11 +320,11 @@ public class GetAddressCmdletTests : IDisposable // Act var action = async () => await RequestHelper.InvokeRequest( "GET", - controllers.addresses, - types.Address + ApiController.Addresses, + ModelType.Address ); // Assert - await action.Should().ThrowAsync().WithMessage("No session available!"); + await action.Should().ThrowAsync().WithMessage("No session available!"); } } diff --git a/classlib.tests/Cmdlets/NewSessionCmdletTests.cs b/classlib.tests/Cmdlets/NewSessionCmdletTests.cs index 64c8f71..99cfb6c 100644 --- a/classlib.tests/Cmdlets/NewSessionCmdletTests.cs +++ b/classlib.tests/Cmdlets/NewSessionCmdletTests.cs @@ -93,11 +93,18 @@ public class NewSessionCmdletTests : IDisposable session.URL.Should().Be("https://ipam.example.com"); session.AppID.Should().Be("testapp"); session.Token.Should().Be("my-api-token"); - session.AuthType.Should().Be(AuthType.token); + session.AuthType.Should().Be(AuthType.Token); session.Expires.Should().BeNull(); session.Credentials.Should().BeNull(); // Verify it was set as current session SessionManager.CurrentSession.Should().BeSameAs(session); } + + [Fact] + public void NewSessionCmdlet_InheritsFromBaseCmdlet() + { + var cmdlet = new NewSessionCmdlet(); + cmdlet.Should().BeAssignableTo(); + } } diff --git a/classlib.tests/Helpers/RequestHelperTests.cs b/classlib.tests/Helpers/RequestHelperTests.cs index 90616a0..7539339 100644 --- a/classlib.tests/Helpers/RequestHelperTests.cs +++ b/classlib.tests/Helpers/RequestHelperTests.cs @@ -27,9 +27,9 @@ public class RequestHelperTests : IDisposable _mockHandler?.ForceDispose(); } - private void SetupSession(AuthType authType = AuthType.token) + private void SetupSession(AuthType authType = AuthType.Token) { - if (authType == AuthType.token) + if (authType == AuthType.Token) { SessionManager.CreateSessionWithToken("https://ipam.example.com", "testapp", "test-token"); } @@ -37,7 +37,7 @@ public class RequestHelperTests : IDisposable { // For credentials auth, we need to set up a session manually with future expiry var session = new Session( - AuthType.credentials, + AuthType.Credentials, "cred-token", "testapp", "https://ipam.example.com", @@ -61,10 +61,10 @@ public class RequestHelperTests : IDisposable // Arrange - no session set up // Act - var action = async () => await RequestHelper.InvokeRequest("GET", controllers.addresses); + var action = async () => await RequestHelper.InvokeRequest("GET", ApiController.Addresses); // Assert - await action.Should().ThrowAsync().WithMessage("No session available!"); + await action.Should().ThrowAsync().WithMessage("No session available!"); } [Fact] @@ -76,7 +76,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse("{\"id\":1,\"ip\":\"192.168.1.1\"}"); // Act - await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" }); + await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" }); // Assert handler.LastRequest.Should().NotBeNull(); @@ -92,7 +92,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse("[]"); // Act - await RequestHelper.InvokeRequest("GET", controllers.subnets, null, subcontrollers.tags, null, new[] { "1" }); + await RequestHelper.InvokeRequest("GET", ApiController.Subnets, null, ApiSubController.Tags, null, new[] { "1" }); // Assert handler.LastRequest!.RequestUri!.ToString().Should().Contain("/subnets/tags/1/"); @@ -102,12 +102,12 @@ public class RequestHelperTests : IDisposable public async Task InvokeRequest_WithTokenAuth_AddsPhpipamTokenHeader() { // Arrange - SetupSession(AuthType.token); + SetupSession(AuthType.Token); var handler = SetupMockHandler(); handler.WithSuccessResponse("{}"); // Act - await RequestHelper.InvokeRequest("GET", controllers.addresses); + await RequestHelper.InvokeRequest("GET", ApiController.Addresses); // Assert handler.GetLastRequestHeader("phpipam-token").Should().Be("test-token"); @@ -117,12 +117,12 @@ public class RequestHelperTests : IDisposable public async Task InvokeRequest_WithCredentialsAuth_AddsTokenHeader() { // Arrange - SetupSession(AuthType.credentials); + SetupSession(AuthType.Credentials); var handler = SetupMockHandler(); handler.WithSuccessResponse("{}"); // Act - await RequestHelper.InvokeRequest("GET", controllers.addresses); + await RequestHelper.InvokeRequest("GET", ApiController.Addresses); // Assert handler.GetLastRequestHeader("token").Should().Be("cred-token"); @@ -137,7 +137,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse("{}"); // Act - await RequestHelper.InvokeRequest("GET", controllers.addresses); + await RequestHelper.InvokeRequest("GET", ApiController.Addresses); // Assert handler.LastRequest!.Method.Should().Be(HttpMethod.Get); @@ -152,7 +152,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse("{\"id\":1}"); // Act - await RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, new { ip = "10.0.0.1" }); + await RequestHelper.InvokeRequest("POST", ApiController.Addresses, null, null, new { ip = "10.0.0.1" }); // Assert handler.LastRequest!.Method.Should().Be(HttpMethod.Post); @@ -167,7 +167,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse("{\"id\":1}"); // Act - await RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, new { description = "updated" }, new[] { "1" }); + await RequestHelper.InvokeRequest("PATCH", ApiController.Addresses, null, null, new { description = "updated" }, new[] { "1" }); // Assert handler.LastRequest!.Method.Should().Be(new HttpMethod("PATCH")); @@ -182,7 +182,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse("{}"); // Act - await RequestHelper.InvokeRequest("DELETE", controllers.addresses, null, null, null, new[] { "1" }); + await RequestHelper.InvokeRequest("DELETE", ApiController.Addresses, null, null, null, new[] { "1" }); // Assert handler.LastRequest!.Method.Should().Be(HttpMethod.Delete); @@ -197,7 +197,7 @@ public class RequestHelperTests : IDisposable handler.WithNotFoundResponse(); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "999" }); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "999" }); // Assert result.Should().BeNull(); @@ -212,7 +212,7 @@ public class RequestHelperTests : IDisposable handler.WithErrorResponse(HttpStatusCode.InternalServerError, "Server error"); // Act - var action = async () => await RequestHelper.InvokeRequest("GET", controllers.addresses); + var action = async () => await RequestHelper.InvokeRequest("GET", ApiController.Addresses); // Assert await action.Should().ThrowAsync(); @@ -249,7 +249,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse(addressJson); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" }); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" }); // Assert result.Should().BeOfType
(); @@ -272,7 +272,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse(addressArrayJson); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.subnets, types.Address, null, null, new[] { "10", "addresses" }); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Subnets, ModelType.Address, null, null, new[] { "10", "addresses" }); // Assert result.Should().BeAssignableTo>(); @@ -300,7 +300,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse(vlanJson); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.vlan, types.Vlan, null, null, new[] { "100" }); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Vlan, ModelType.Vlan, null, null, new[] { "100" }); // Assert result.Should().BeOfType(); @@ -351,14 +351,14 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse(subnetJson); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "1" }); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Subnets, ModelType.Subnetwork, null, null, new[] { "1" }); // Assert result.Should().BeOfType(); var subnet = (Subnetwork)result!; subnet.Subnet.Should().Be("192.168.1.0"); subnet.Mask.Should().Be(24); - subnet.GetCIDR().Should().Be("192.168.1.0/24"); + subnet.CIDR.Should().Be("192.168.1.0/24"); } [Fact] @@ -386,7 +386,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse(sectionJson); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { "1" }); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Sections, ModelType.Section, null, null, new[] { "1" }); // Assert result.Should().BeOfType
(); @@ -411,12 +411,12 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse(addressJson); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" }); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address, null, null, new[] { "1" }); // Assert var address = (Address)result!; address.ExtendedData.Should().NotBeNull(); - var extendedData = (Dictionary)address.ExtendedData!; + var extendedData = address.ExtendedData!; extendedData.Should().ContainKey("custom_environment"); extendedData.Should().ContainKey("custom_owner"); } @@ -430,7 +430,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse(@"{""some"": ""data""}"); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.tools); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Tools); // Assert result.Should().NotBeNull(); @@ -447,7 +447,7 @@ public class RequestHelperTests : IDisposable var parameters = new { ip = "10.0.0.1", subnetId = 5, description = "New address" }; // Act - await RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, parameters); + await RequestHelper.InvokeRequest("POST", ApiController.Addresses, null, null, parameters); // Assert handler.LastRequest!.Content.Should().NotBeNull(); @@ -465,7 +465,7 @@ public class RequestHelperTests : IDisposable handler.WithSuccessResponse("{}"); // Act - await RequestHelper.InvokeRequest("GET", controllers.addresses); + await RequestHelper.InvokeRequest("GET", ApiController.Addresses); // Assert handler.LastRequest!.Headers.Accept.Should().Contain(h => h.MediaType == "application/json"); @@ -480,7 +480,7 @@ public class RequestHelperTests : IDisposable handler.WithResponse(HttpStatusCode.OK, "", "application/json"); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.addresses); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses); // Assert result.Should().BeNull(); @@ -495,7 +495,7 @@ public class RequestHelperTests : IDisposable handler.WithJsonResponse(@"{""code"": 200, ""success"": true, ""data"": null}"); // Act - var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address); + var result = await RequestHelper.InvokeRequest("GET", ApiController.Addresses, ModelType.Address); // Assert result.Should().BeNull(); diff --git a/classlib.tests/Helpers/SessionManagerTests.cs b/classlib.tests/Helpers/SessionManagerTests.cs index d521863..cb15994 100644 --- a/classlib.tests/Helpers/SessionManagerTests.cs +++ b/classlib.tests/Helpers/SessionManagerTests.cs @@ -21,60 +21,60 @@ public class SessionManagerTests : IDisposable } [Fact] - public void TestSession_WhenNoSession_ReturnsNoToken() + public void GetSessionStatus_WhenNoSession_ReturnsNoSession() { // Arrange SessionManager.CurrentSession = null; // Act - var result = SessionManager.TestSession(); + var result = SessionManager.GetSessionStatus(); // Assert - result.Should().Be("NoToken"); + result.Should().Be(SessionStatus.NoSession); } [Fact] - public void TestSession_WhenSessionWithNullExpires_ReturnsValid() + public void GetSessionStatus_WhenSessionWithNullExpires_ReturnsValid() { // Arrange - var session = new Session(AuthType.token, "test-token", "app", "https://test.com", null, null); + var session = new Session(AuthType.Token, "test-token", "app", "https://test.com", null, null); SessionManager.CurrentSession = session; // Act - var result = SessionManager.TestSession(); + var result = SessionManager.GetSessionStatus(); // Assert - result.Should().Be("Valid"); + result.Should().Be(SessionStatus.Valid); } [Fact] - public void TestSession_WhenSessionNotExpired_ReturnsValid() + public void GetSessionStatus_WhenSessionNotExpired_ReturnsValid() { // Arrange var futureExpiry = DateTime.Now.AddHours(1); - var session = new Session(AuthType.credentials, "test-token", "app", "https://test.com", futureExpiry, null); + var session = new Session(AuthType.Credentials, "test-token", "app", "https://test.com", futureExpiry, null); SessionManager.CurrentSession = session; // Act - var result = SessionManager.TestSession(); + var result = SessionManager.GetSessionStatus(); // Assert - result.Should().Be("Valid"); + result.Should().Be(SessionStatus.Valid); } [Fact] - public void TestSession_WhenSessionExpired_ReturnsExpired() + public void GetSessionStatus_WhenSessionExpired_ReturnsExpired() { // Arrange var pastExpiry = DateTime.Now.AddHours(-1); - var session = new Session(AuthType.credentials, "test-token", "app", "https://test.com", pastExpiry, null); + var session = new Session(AuthType.Credentials, "test-token", "app", "https://test.com", pastExpiry, null); SessionManager.CurrentSession = session; // Act - var result = SessionManager.TestSession(); + var result = SessionManager.GetSessionStatus(); // Assert - result.Should().Be("Expired"); + result.Should().Be(SessionStatus.Expired); } [Fact] @@ -93,7 +93,7 @@ public class SessionManagerTests : IDisposable session.URL.Should().Be(url); session.AppID.Should().Be(appId); session.Token.Should().Be(token); - session.AuthType.Should().Be(AuthType.token); + session.AuthType.Should().Be(AuthType.Token); session.Expires.Should().BeNull(); session.Credentials.Should().BeNull(); } @@ -144,7 +144,7 @@ public class SessionManagerTests : IDisposable public void CurrentSession_CanBeSetDirectly() { // Arrange - var session = new Session(AuthType.token, "token", "app", "https://test.com", null, null); + var session = new Session(AuthType.Token, "token", "app", "https://test.com", null, null); // Act SessionManager.CurrentSession = session; diff --git a/classlib.tests/Models/AddressTests.cs b/classlib.tests/Models/AddressTests.cs index 60ec426..72edfd1 100644 --- a/classlib.tests/Models/AddressTests.cs +++ b/classlib.tests/Models/AddressTests.cs @@ -55,7 +55,7 @@ public class AddressTests address.Note.Should().Be(note); address.LastSeen.Should().Be(lastSeen); address.ExcludePing.Should().Be(excludePing); - address.PTRignore.Should().Be(ptrIgnore); + address.PTRIgnore.Should().Be(ptrIgnore); address.PTR.Should().Be(ptr); address.FirewallAddressObject.Should().Be(firewallObject); address.EditDate.Should().Be(editDate); @@ -113,4 +113,24 @@ public class AddressTests // Act & Assert address.ToString().Should().Be(expectedIp); } + + [Fact] + public void Record_Equality_WorksCorrectly() + { + // Arrange + var address1 = new Address( + 1, 10, "192.168.1.1", false, "Test", "host", "", "", + 0, 0, "", "", "", null, false, + false, 0, "", null, 0, null + ); + + var address2 = new Address( + 1, 10, "192.168.1.1", false, "Test", "host", "", "", + 0, 0, "", "", "", null, false, + false, 0, "", null, 0, null + ); + + // Assert + address1.Should().Be(address2); + } } diff --git a/classlib.tests/Models/NameserverTests.cs b/classlib.tests/Models/NameserverTests.cs index 570cd9f..def212c 100644 --- a/classlib.tests/Models/NameserverTests.cs +++ b/classlib.tests/Models/NameserverTests.cs @@ -57,7 +57,7 @@ public class NameserverTests } [Fact] - public void Constructor_WithEmptyNameservers_ReturnsArrayWithEmptyString() + public void Constructor_WithEmptyNameservers_ReturnsEmptyArray() { // Arrange var nameServersString = ""; @@ -65,9 +65,8 @@ public class NameserverTests // Act var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null); - // Assert - nameserver.NameServers.Should().HaveCount(1); - nameserver.NameServers[0].Should().BeEmpty(); + // Assert - Empty entries are excluded + nameserver.NameServers.Should().BeEmpty(); } [Fact] @@ -79,4 +78,27 @@ public class NameserverTests // Assert nameserver.EditDate.Should().BeNull(); } + + [Fact] + public void Constructor_WithSemicolonOnlyString_ReturnsEmptyArray() + { + // Arrange - edge case with just semicolons + var nameServersString = ";;;"; + + // Act + var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null); + + // Assert + nameserver.NameServers.Should().BeEmpty(); + } + + [Fact] + public void ToString_ReturnsName() + { + // Arrange + var nameserver = new Nameserver(1, "Google DNS", "8.8.8.8", "", "", null); + + // Act & Assert + nameserver.ToString().Should().Be("Google DNS"); + } } diff --git a/classlib.tests/Models/SessionTests.cs b/classlib.tests/Models/SessionTests.cs index 4d9ebb2..79fa200 100644 --- a/classlib.tests/Models/SessionTests.cs +++ b/classlib.tests/Models/SessionTests.cs @@ -10,7 +10,7 @@ public class SessionTests public void Constructor_WithCredentialsAuth_SetsAllProperties() { // Arrange - var authType = AuthType.credentials; + var authType = AuthType.Credentials; var token = "test-token-123"; var appId = "myApp"; var url = "https://ipam.example.com"; @@ -21,7 +21,7 @@ public class SessionTests var session = new Session(authType, token, appId, url, expires, credentials); // Assert - session.AuthType.Should().Be(AuthType.credentials); + session.AuthType.Should().Be(AuthType.Credentials); session.Token.Should().Be(token); session.AppID.Should().Be(appId); session.URL.Should().Be(url); @@ -33,7 +33,7 @@ public class SessionTests public void Constructor_WithTokenAuth_SetsAllProperties() { // Arrange - var authType = AuthType.token; + var authType = AuthType.Token; var token = "static-api-token"; var appId = "apiApp"; var url = "https://ipam.test.com"; @@ -42,7 +42,7 @@ public class SessionTests var session = new Session(authType, token, appId, url, null, null); // Assert - session.AuthType.Should().Be(AuthType.token); + session.AuthType.Should().Be(AuthType.Token); session.Token.Should().Be(token); session.AppID.Should().Be(appId); session.URL.Should().Be(url); @@ -54,7 +54,7 @@ public class SessionTests public void Token_CanBeModified() { // Arrange - var session = new Session(AuthType.credentials, "old-token", "app", "https://test.com", null, null); + var session = new Session(AuthType.Credentials, "old-token", "app", "https://test.com", null, null); // Act session.Token = "new-token"; @@ -67,7 +67,7 @@ public class SessionTests public void Expires_CanBeModified() { // Arrange - var session = new Session(AuthType.credentials, "token", "app", "https://test.com", null, null); + var session = new Session(AuthType.Credentials, "token", "app", "https://test.com", null, null); var newExpiry = new DateTime(2027, 1, 1); // Act @@ -78,15 +78,13 @@ public class SessionTests } [Fact] - public void ToString_ReturnsDefaultObjectString() + public void Record_Equality_WorksForSameValues() { // Arrange - var session = new Session(AuthType.token, "token", "app", "https://test.com", null, null); + var session1 = new Session(AuthType.Token, "token", "app", "https://test.com", null, null); + var session2 = new Session(AuthType.Token, "token", "app", "https://test.com", null, null); - // Act - var result = session.ToString(); - - // Assert - result.Should().Contain("PS.IPAM.Session"); + // Assert - Records use value equality + session1.Should().Be(session2); } } diff --git a/classlib.tests/Models/SubnetworkTests.cs b/classlib.tests/Models/SubnetworkTests.cs index eca5166..bc3ec51 100644 --- a/classlib.tests/Models/SubnetworkTests.cs +++ b/classlib.tests/Models/SubnetworkTests.cs @@ -93,13 +93,13 @@ public class SubnetworkTests [InlineData("10.0.0.0", 8, "10.0.0.0/8")] [InlineData("172.16.0.0", 16, "172.16.0.0/16")] [InlineData("192.168.100.0", 30, "192.168.100.0/30")] - public void GetCIDR_ReturnsCidrNotation(string subnet, int mask, string expectedCidr) + public void CIDR_ReturnsCidrNotation(string subnet, int mask, string expectedCidr) { // Arrange var subnetwork = CreateSubnetwork(subnet, mask); // Act - var result = subnetwork.GetCIDR(); + var result = subnetwork.CIDR; // Assert result.Should().Be(expectedCidr); @@ -119,16 +119,31 @@ public class SubnetworkTests } [Fact] - public void ToString_EqualsGetCIDR() + public void ToString_EqualsCIDRProperty() { // Arrange var subnetwork = CreateSubnetwork("172.20.0.0", 12); // Act & Assert - subnetwork.ToString().Should().Be(subnetwork.GetCIDR()); + subnetwork.ToString().Should().Be(subnetwork.CIDR); } - private static Subnetwork CreateSubnetwork(string subnet, int mask) + [Fact] + public void Record_Equality_WorksCorrectly() + { + // Arrange - use same calculation object for equality comparison + var calculation = new { maxhosts = 254 }; + var subnet1 = CreateSubnetwork("192.168.1.0", 24, calculation); + var subnet2 = CreateSubnetwork("192.168.1.0", 24, calculation); + + // Assert - records use value equality (except for the Calculation object reference) + subnet1.Id.Should().Be(subnet2.Id); + subnet1.Subnet.Should().Be(subnet2.Subnet); + subnet1.Mask.Should().Be(subnet2.Mask); + subnet1.CIDR.Should().Be(subnet2.CIDR); + } + + private static Subnetwork CreateSubnetwork(string subnet, int mask, object? calculation = null) { return new Subnetwork( 1, subnet, mask, 1, "", "", "", @@ -136,7 +151,7 @@ public class SubnetworkTests "", false, false, false, false, false, 0, false, false, false, false, 0, 0, 0, null, null, null, - new object(), null + calculation ?? new object(), null ); } } diff --git a/classlib.tests/Models/TagTests.cs b/classlib.tests/Models/TagTests.cs index db345ef..a40010b 100644 --- a/classlib.tests/Models/TagTests.cs +++ b/classlib.tests/Models/TagTests.cs @@ -26,28 +26,13 @@ public class TagTests tag.Id.Should().Be(id); tag.Type.Should().Be(type); tag.ShowTag.Should().Be(showTag); - tag.BGColor.Should().Be(bgColor); - tag.FGColor.Should().Be(fgColor); - tag.Compress.Should().BeTrue(); - tag.Locked.Should().BeFalse(); + tag.BackgroundColor.Should().Be(bgColor); + tag.ForegroundColor.Should().Be(fgColor); + tag.Compress.Should().Be(compress); + tag.Locked.Should().Be(locked); tag.UpdateTag.Should().Be(updateTag); } - [Theory] - [InlineData("Yes", true)] - [InlineData("No", false)] - [InlineData("", false)] - [InlineData("yes", false)] // Case sensitive - [InlineData("true", false)] // Only "Yes" is true - public void StringToBool_ConvertsCorrectly(string input, bool expected) - { - // The StringToBool is private, so we test through the constructor - // Using Compress field which uses StringToBool - var tag = new Tag(1, "Test", false, "", "", input, "No", false); - - tag.Compress.Should().Be(expected); - } - [Theory] [InlineData("Used")] [InlineData("Available")] @@ -66,12 +51,13 @@ public class TagTests } [Fact] - public void Locked_WithYes_IsTrue() + public void Record_Equality_WorksCorrectly() { - // Arrange & Act - var tag = new Tag(1, "Test", false, "", "", "No", "Yes", false); + // Arrange + var tag1 = new Tag(1, "Used", true, "#fff", "#000", "Yes", "No", false); + var tag2 = new Tag(1, "Used", true, "#fff", "#000", "Yes", "No", false); // Assert - tag.Locked.Should().BeTrue(); + tag1.Should().Be(tag2); } } diff --git a/classlib/Cmdlets/AssignTagCmdlet.cs b/classlib/Cmdlets/AssignTagCmdlet.cs index 5ac2a64..a506450 100644 --- a/classlib/Cmdlets/AssignTagCmdlet.cs +++ b/classlib/Cmdlets/AssignTagCmdlet.cs @@ -1,24 +1,30 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; -[Cmdlet("Assign", "Tag")] -public class AssignTagCmdlet : PSCmdlet +/// +/// Assigns a tag to an address in phpIPAM. +/// +[Cmdlet("Assign", "Tag", SupportsShouldProcess = true)] +[OutputType(typeof(Address))] +public class AssignTagCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 0)] + Position = 0, + HelpMessage = "The address to assign the tag to.")] [ValidateNotNullOrEmpty] public Address? AddressObject { get; set; } [Parameter( Mandatory = true, - Position = 1)] + Position = 1, + HelpMessage = "The tag to assign.")] [ValidateNotNullOrEmpty] public Tag? Tag { get; set; } @@ -26,14 +32,25 @@ public class AssignTagCmdlet : PSCmdlet { try { - var identifiers = new List { AddressObject!.Id.ToString() }; + var identifiers = new[] { AddressObject!.Id.ToString() }; var body = new Dictionary { - { "tag", Tag!.Id } + ["tag"] = Tag!.Id }; - RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, body, identifiers.ToArray()) - .GetAwaiter().GetResult(); + if (ShouldProcess($"Address {AddressObject.Ip}", $"Assign tag '{Tag.Type}'")) + { + RequestHelper.InvokeRequest( + "PATCH", ApiController.Addresses, null, null, body, identifiers + ).GetAwaiter().GetResult(); + + // Return the updated address + var address = RequestHelper.InvokeRequest( + "GET", ApiController.Addresses, ModelType.Address, null, null, identifiers + ).GetAwaiter().GetResult(); + + WriteResult(address); + } } catch (Exception ex) { diff --git a/classlib/Cmdlets/BaseCmdlet.cs b/classlib/Cmdlets/BaseCmdlet.cs new file mode 100644 index 0000000..83dc9d3 --- /dev/null +++ b/classlib/Cmdlets/BaseCmdlet.cs @@ -0,0 +1,114 @@ +namespace PS.IPAM.Cmdlets; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; +using PS.IPAM.Helpers; + +/// +/// Base class for all PS.IPAM cmdlets providing common functionality. +/// +public abstract class BaseCmdlet : PSCmdlet +{ + /// + /// Writes the result to the pipeline, handling both single objects and collections. + /// + /// The result to write to the pipeline. + protected void WriteResult(object? result) + { + if (result == null) + { + return; + } + + if (result is IEnumerable enumerable && result is not string) + { + foreach (var item in enumerable) + { + WriteObject(item); + } + } + else + { + WriteObject(result); + } + } + + /// + /// Converts custom fields from various formats to a dictionary. + /// + /// The custom fields object to convert. + /// A dictionary of custom field names and values. + protected static Dictionary ConvertCustomFields(object? customFields) + { + if (customFields == null) + { + return new Dictionary(); + } + + if (customFields is PSObject psobj) + { + var dict = new Dictionary(); + foreach (var prop in psobj.Properties) + { + dict[prop.Name] = prop.Value ?? string.Empty; + } + return dict; + } + + if (customFields is Dictionary dictObj) + { + return dictObj; + } + + if (customFields is IDictionary genericDict) + { + var result = new Dictionary(); + foreach (DictionaryEntry entry in genericDict) + { + result[entry.Key?.ToString() ?? string.Empty] = entry.Value ?? string.Empty; + } + return result; + } + + return new Dictionary(); + } + + /// + /// Executes an async operation synchronously and handles errors. + /// + /// The return type of the operation. + /// The async operation to execute. + /// The error ID for error reporting. + /// The result of the operation, or default if an error occurred. + protected T? ExecuteAsync(Func> operation, string errorId) + { + try + { + return operation().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, errorId, ErrorCategory.InvalidOperation, null)); + return default; + } + } + + /// + /// Executes an async operation synchronously and handles errors. + /// + /// The async operation to execute. + /// The error ID for error reporting. + protected void ExecuteAsync(Func operation, string errorId) + { + try + { + operation().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, errorId, ErrorCategory.InvalidOperation, null)); + } + } +} diff --git a/classlib/Cmdlets/CloseSessionCmdlet.cs b/classlib/Cmdlets/CloseSessionCmdlet.cs index 07dabda..e5ee888 100644 --- a/classlib/Cmdlets/CloseSessionCmdlet.cs +++ b/classlib/Cmdlets/CloseSessionCmdlet.cs @@ -1,21 +1,17 @@ namespace PS.IPAM.Cmdlets; + using System.Management.Automation; using PS.IPAM.Helpers; +/// +/// Closes the current phpIPAM API session. +/// [Cmdlet(VerbsCommon.Close, "Session")] -public class CloseSessionCmdlet : PSCmdlet +public class CloseSessionCmdlet : BaseCmdlet { protected override void ProcessRecord() { - try - { - RequestHelper.InvokeRequest("DELETE", controllers.user, null, null, null, null) - .GetAwaiter().GetResult(); - SessionManager.CloseSession(); - } - catch (System.Exception ex) - { - WriteError(new ErrorRecord(ex, "CloseSessionError", ErrorCategory.InvalidOperation, null)); - } + SessionManager.CloseSession(); + WriteVerbose("Session closed successfully."); } } diff --git a/classlib/Cmdlets/GetAddressCmdlet.cs b/classlib/Cmdlets/GetAddressCmdlet.cs index b45d68d..9433c47 100644 --- a/classlib/Cmdlets/GetAddressCmdlet.cs +++ b/classlib/Cmdlets/GetAddressCmdlet.cs @@ -1,21 +1,25 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; using System.Net; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves IP address entries from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Address", DefaultParameterSetName = "ByID")] [OutputType(typeof(Address))] -public class GetAddressCmdlet : PSCmdlet +public class GetAddressCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The address ID to retrieve.")] [ValidateNotNullOrEmpty] public int Id { get; set; } @@ -24,7 +28,8 @@ public class GetAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByIP")] + ParameterSetName = "ByIP", + HelpMessage = "The IP address to search for.")] [Parameter( Mandatory = false, ValueFromPipeline = true, @@ -39,7 +44,8 @@ public class GetAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByHostName")] + ParameterSetName = "ByHostName", + HelpMessage = "The hostname to search for.")] [ValidateNotNullOrEmpty] public string? HostName { get; set; } @@ -48,7 +54,8 @@ public class GetAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByHostBase")] + ParameterSetName = "ByHostBase", + HelpMessage = "The hostname base pattern to search for.")] [ValidateNotNullOrEmpty] public string? HostBase { get; set; } @@ -57,7 +64,8 @@ public class GetAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByTag")] + ParameterSetName = "ByTag", + HelpMessage = "The tag ID to filter addresses by.")] [ValidateNotNullOrEmpty] public int? TagId { get; set; } @@ -66,7 +74,8 @@ public class GetAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetId")] + ParameterSetName = "BySubnetId", + HelpMessage = "The subnet ID to get addresses from.")] [ValidateNotNullOrEmpty] public int? SubnetId { get; set; } @@ -75,7 +84,8 @@ public class GetAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetCIDR")] + ParameterSetName = "BySubnetCIDR", + HelpMessage = "The subnet in CIDR notation to get addresses from.")] [ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")] [ValidateNotNullOrEmpty] public string? SubnetCIDR { get; set; } @@ -84,7 +94,7 @@ public class GetAddressCmdlet : PSCmdlet { try { - var controller = controllers.addresses; + var controller = ApiController.Addresses; var identifiers = new List(); switch (ParameterSetName) @@ -92,23 +102,28 @@ public class GetAddressCmdlet : PSCmdlet case "ByID": identifiers.Add(Id.ToString()); break; + case "ByIP": identifiers.Add("search"); identifiers.Add(IP!.ToString()); break; + case "ByHostName": identifiers.Add("search_hostname"); identifiers.Add(HostName!); break; + case "ByHostBase": identifiers.Add("search_hostbase"); identifiers.Add(HostBase!); break; + case "ByTag": identifiers.Add("tags"); identifiers.Add(TagId!.Value.ToString()); identifiers.Add("addresses"); break; + case "BySubnetId": if (IP != null) { @@ -117,42 +132,34 @@ public class GetAddressCmdlet : PSCmdlet } else { - controller = controllers.subnets; + controller = ApiController.Subnets; identifiers.Add(SubnetId!.Value.ToString()); identifiers.Add("addresses"); } break; + case "BySubnetCIDR": - controller = controllers.subnets; - var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", SubnetCIDR! }) - .GetAwaiter().GetResult(); + controller = ApiController.Subnets; + var subnet = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, ModelType.Subnetwork, null, null, + new[] { "cidr", SubnetCIDR! } + ).GetAwaiter().GetResult() as Subnetwork; + if (subnet == null) { - throw new Exception("Cannot find subnet!"); + throw new ItemNotFoundException($"Subnet '{SubnetCIDR}' not found."); } - var subnetObj = subnet as Subnetwork; - identifiers.Add(subnetObj!.Id.ToString()); + + identifiers.Add(subnet.Id.ToString()); identifiers.Add("addresses"); break; } - var result = RequestHelper.InvokeRequest("GET", controller, types.Address, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", controller, ModelType.Address, null, null, identifiers.ToArray() + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { diff --git a/classlib/Cmdlets/GetFirstFreeIPCmdlet.cs b/classlib/Cmdlets/GetFirstFreeIPCmdlet.cs index ffd8ab8..e096077 100644 --- a/classlib/Cmdlets/GetFirstFreeIPCmdlet.cs +++ b/classlib/Cmdlets/GetFirstFreeIPCmdlet.cs @@ -1,20 +1,23 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using System.Net; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Gets the first available IP address in a subnet. +/// [Cmdlet(VerbsCommon.Get, "FirstFreeIP", DefaultParameterSetName = "ByID")] -public class GetFirstFreeIPCmdlet : PSCmdlet +public class GetFirstFreeIPCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByCIDR")] + ParameterSetName = "ByCIDR", + HelpMessage = "The subnet in CIDR notation.")] [ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")] [ValidateNotNullOrEmpty] public string? CIDR { get; set; } @@ -24,7 +27,8 @@ public class GetFirstFreeIPCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The subnet ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -33,7 +37,8 @@ public class GetFirstFreeIPCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetObject")] + ParameterSetName = "BySubnetObject", + HelpMessage = "The subnet object.")] [ValidateNotNullOrEmpty] public Subnetwork? SubnetObject { get; set; } @@ -41,30 +46,12 @@ public class GetFirstFreeIPCmdlet : PSCmdlet { try { - int subnetId; - if (ParameterSetName == "ByCIDR") - { - var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", CIDR! }) - .GetAwaiter().GetResult(); - if (subnet == null) - { - throw new Exception("Cannot find subnet!"); - } - var subnetObj = subnet as Subnetwork; - subnetId = subnetObj!.Id; - } - else if (ParameterSetName == "BySubnetObject") - { - subnetId = SubnetObject!.Id; - } - else - { - subnetId = Id!.Value; - } - + var subnetId = GetSubnetId(); var identifiers = new List { subnetId.ToString(), "first_free" }; - var result = RequestHelper.InvokeRequest("GET", controllers.subnets, null, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); + + var result = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, null, null, null, identifiers.ToArray() + ).GetAwaiter().GetResult(); if (result != null) { @@ -76,4 +63,28 @@ public class GetFirstFreeIPCmdlet : PSCmdlet WriteError(new ErrorRecord(ex, "GetFirstFreeIPError", ErrorCategory.InvalidOperation, null)); } } + + private int GetSubnetId() + { + switch (ParameterSetName) + { + case "ByCIDR": + var subnet = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, ModelType.Subnetwork, null, null, + new[] { "cidr", CIDR! } + ).GetAwaiter().GetResult() as Subnetwork; + + if (subnet == null) + { + throw new ItemNotFoundException($"Subnet '{CIDR}' not found."); + } + return subnet.Id; + + case "BySubnetObject": + return SubnetObject!.Id; + + default: + return Id!.Value; + } + } } diff --git a/classlib/Cmdlets/GetL2DomainCmdlet.cs b/classlib/Cmdlets/GetL2DomainCmdlet.cs index 4839d56..e66918d 100644 --- a/classlib/Cmdlets/GetL2DomainCmdlet.cs +++ b/classlib/Cmdlets/GetL2DomainCmdlet.cs @@ -1,20 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves L2 domain information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "L2Domain", DefaultParameterSetName = "ByID")] [OutputType(typeof(Domain))] -public class GetL2DomainCmdlet : PSCmdlet +public class GetL2DomainCmdlet : BaseCmdlet { [Parameter( Mandatory = false, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The L2 domain ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -23,28 +27,18 @@ public class GetL2DomainCmdlet : PSCmdlet try { var identifiers = new List(); + if (Id.HasValue) { identifiers.Add(Id.Value.ToString()); } - var result = RequestHelper.InvokeRequest("GET", controllers.l2domains, types.Domain, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", ApiController.L2Domains, ModelType.Domain, null, null, + identifiers.Count > 0 ? identifiers.ToArray() : null + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { diff --git a/classlib/Cmdlets/GetNameserverCmdlet.cs b/classlib/Cmdlets/GetNameserverCmdlet.cs index 2bb91f5..7533038 100644 --- a/classlib/Cmdlets/GetNameserverCmdlet.cs +++ b/classlib/Cmdlets/GetNameserverCmdlet.cs @@ -1,20 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves nameserver information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Nameserver", DefaultParameterSetName = "NoParams")] [OutputType(typeof(Nameserver))] -public class GetNameserverCmdlet : PSCmdlet +public class GetNameserverCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The nameserver ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -23,28 +27,18 @@ public class GetNameserverCmdlet : PSCmdlet try { var identifiers = new List(); + if (Id.HasValue) { identifiers.Add(Id.Value.ToString()); } - var result = RequestHelper.InvokeRequest("GET", controllers.tools, types.Nameserver, subcontrollers.nameservers, null, identifiers.Count > 0 ? identifiers.ToArray() : null) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", ApiController.Tools, ModelType.Nameserver, ApiSubController.Nameservers, + null, identifiers.Count > 0 ? identifiers.ToArray() : null + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { diff --git a/classlib/Cmdlets/GetPermissionsCmdlet.cs b/classlib/Cmdlets/GetPermissionsCmdlet.cs index 4c476fb..de8284f 100644 --- a/classlib/Cmdlets/GetPermissionsCmdlet.cs +++ b/classlib/Cmdlets/GetPermissionsCmdlet.cs @@ -1,20 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; using System.Net; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves permission information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Permissions")] -public class GetPermissionsCmdlet : PSCmdlet +public class GetPermissionsCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The address ID.")] [ValidateNotNullOrEmpty] public string? Id { get; set; } @@ -23,7 +27,8 @@ public class GetPermissionsCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByIP")] + ParameterSetName = "ByIP", + HelpMessage = "The IP address to search for.")] [Parameter( Mandatory = false, ValueFromPipeline = true, @@ -38,7 +43,8 @@ public class GetPermissionsCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByHostName")] + ParameterSetName = "ByHostName", + HelpMessage = "The hostname to search for.")] [ValidateNotNullOrEmpty] public string? HostName { get; set; } @@ -47,7 +53,8 @@ public class GetPermissionsCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByTag")] + ParameterSetName = "ByTag", + HelpMessage = "The tag ID.")] [ValidateNotNullOrEmpty] public string? TagId { get; set; } @@ -56,7 +63,8 @@ public class GetPermissionsCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetId")] + ParameterSetName = "BySubnetId", + HelpMessage = "The subnet ID.")] [ValidateNotNullOrEmpty] public string? SubnetId { get; set; } @@ -65,7 +73,8 @@ public class GetPermissionsCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetCIDR")] + ParameterSetName = "BySubnetCIDR", + HelpMessage = "The subnet in CIDR notation.")] [ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")] [ValidateNotNullOrEmpty] public string? SubnetCIDR { get; set; } @@ -74,7 +83,7 @@ public class GetPermissionsCmdlet : PSCmdlet { try { - var controller = controllers.addresses; + var controller = ApiController.Addresses; var identifiers = new List(); switch (ParameterSetName) @@ -82,19 +91,23 @@ public class GetPermissionsCmdlet : PSCmdlet case "ByID": identifiers.Add(Id!); break; + case "ByIP": identifiers.Add("search"); identifiers.Add(IP!.ToString()); break; + case "ByHostName": identifiers.Add("search_hostname"); identifiers.Add(HostName!); break; + case "ByTag": identifiers.Add("tags"); identifiers.Add(TagId!); identifiers.Add("addresses"); break; + case "BySubnetId": if (IP != null) { @@ -103,42 +116,34 @@ public class GetPermissionsCmdlet : PSCmdlet } else { - controller = controllers.subnets; + controller = ApiController.Subnets; identifiers.Add(SubnetId!); identifiers.Add("addresses"); } break; + case "BySubnetCIDR": - controller = controllers.subnets; - var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", SubnetCIDR! }) - .GetAwaiter().GetResult(); + controller = ApiController.Subnets; + var subnet = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, ModelType.Subnetwork, null, null, + new[] { "cidr", SubnetCIDR! } + ).GetAwaiter().GetResult() as Subnetwork; + if (subnet == null) { - throw new Exception("Cannot find subnet!"); + throw new ItemNotFoundException($"Subnet '{SubnetCIDR}' not found."); } - var subnetObj = subnet as Subnetwork; - identifiers.Add(subnetObj!.Id.ToString()); + + identifiers.Add(subnet.Id.ToString()); identifiers.Add("addresses"); break; } - var result = RequestHelper.InvokeRequest("GET", controller, null, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", controller, null, null, null, identifiers.ToArray() + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { diff --git a/classlib/Cmdlets/GetSectionCmdlet.cs b/classlib/Cmdlets/GetSectionCmdlet.cs index 97dd9e2..50fc250 100644 --- a/classlib/Cmdlets/GetSectionCmdlet.cs +++ b/classlib/Cmdlets/GetSectionCmdlet.cs @@ -1,19 +1,23 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves section information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Section", DefaultParameterSetName = "NoParams")] [OutputType(typeof(Section))] -public class GetSectionCmdlet : PSCmdlet +public class GetSectionCmdlet : BaseCmdlet { [Parameter( Mandatory = false, ValueFromPipeline = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The section ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -21,7 +25,8 @@ public class GetSectionCmdlet : PSCmdlet Mandatory = true, ValueFromPipeline = true, Position = 0, - ParameterSetName = "ByName")] + ParameterSetName = "ByName", + HelpMessage = "The section name.")] [ValidateNotNullOrEmpty] public string? Name { get; set; } @@ -30,6 +35,7 @@ public class GetSectionCmdlet : PSCmdlet try { var identifiers = new List(); + if (ParameterSetName == "ByID" && Id.HasValue) { identifiers.Add(Id.Value.ToString()); @@ -39,23 +45,12 @@ public class GetSectionCmdlet : PSCmdlet identifiers.Add(Name!); } - var result = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", ApiController.Sections, ModelType.Section, null, null, + identifiers.Count > 0 ? identifiers.ToArray() : null + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { diff --git a/classlib/Cmdlets/GetSubnetCmdlet.cs b/classlib/Cmdlets/GetSubnetCmdlet.cs index d2c3f52..0f08d3e 100644 --- a/classlib/Cmdlets/GetSubnetCmdlet.cs +++ b/classlib/Cmdlets/GetSubnetCmdlet.cs @@ -1,22 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; -using System.Linq; using System.Management.Automation; -using System.Net; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves subnet information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Subnet", DefaultParameterSetName = "NoParams")] [OutputType(typeof(Subnetwork))] -public class GetSubnetCmdlet : PSCmdlet +public class GetSubnetCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByCIDR")] + ParameterSetName = "ByCIDR", + HelpMessage = "The subnet in CIDR notation (e.g., 192.168.1.0/24).")] [ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")] [ValidateNotNullOrEmpty] public string? CIDR { get; set; } @@ -26,7 +28,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The subnet ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -35,7 +38,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySectionId")] + ParameterSetName = "BySectionId", + HelpMessage = "The section ID to get subnets from.")] [Parameter( Mandatory = false, ValueFromPipeline = true, @@ -56,7 +60,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySectionName")] + ParameterSetName = "BySectionName", + HelpMessage = "The section name to get subnets from.")] [Parameter( Mandatory = false, ValueFromPipeline = true, @@ -77,7 +82,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByVrfId")] + ParameterSetName = "ByVrfId", + HelpMessage = "The VRF ID to get subnets from.")] [ValidateNotNullOrEmpty] public int? VrfId { get; set; } @@ -86,7 +92,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByVlanId")] + ParameterSetName = "ByVlanId", + HelpMessage = "The VLAN ID to get subnets from.")] [ValidateNotNullOrEmpty] public int? VlanId { get; set; } @@ -95,7 +102,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByVlanNumber")] + ParameterSetName = "ByVlanNumber", + HelpMessage = "The VLAN number to get subnets from.")] [ValidateNotNullOrEmpty] public int? VlanNumber { get; set; } @@ -104,7 +112,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 1, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "Get child subnets (slaves).")] public SwitchParameter Slaves { get; set; } [Parameter( @@ -112,7 +121,8 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 2, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "Get child subnets recursively.")] public SwitchParameter Recurse { get; set; } [Parameter( @@ -120,16 +130,16 @@ public class GetSubnetCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 1, - ParameterSetName = "ByVlanNumber")] + ParameterSetName = "ByVlanNumber", + HelpMessage = "The L2 domain ID to narrow VLAN search.")] [ValidateNotNullOrEmpty] public int? VlanDomainId { get; set; } - protected override void ProcessRecord() { try { - var controller = controllers.subnets; + var controller = ApiController.Subnets; var identifiers = new List(); switch (ParameterSetName) @@ -138,6 +148,7 @@ public class GetSubnetCmdlet : PSCmdlet identifiers.Add("cidr"); identifiers.Add(CIDR!); break; + case "ByID": identifiers.Add(Id!.Value.ToString()); if (Slaves.IsPresent) @@ -145,117 +156,122 @@ public class GetSubnetCmdlet : PSCmdlet identifiers.Add(Recurse.IsPresent ? "slaves_recursive" : "slaves"); } break; + case "BySectionId": - controller = controllers.sections; + controller = ApiController.Sections; identifiers.Add(SectionId!.Value.ToString()); identifiers.Add("subnets"); break; + case "BySectionName": - controller = controllers.sections; - var section = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { SectionName! }) - .GetAwaiter().GetResult(); - if (section == null) - { - throw new Exception("Cannot find section!"); - } - var sectionObj = section as Section; - identifiers.Add(sectionObj!.Id.ToString()); + controller = ApiController.Sections; + var section = GetSectionByName(SectionName!); + identifiers.Add(section.Id.ToString()); identifiers.Add("subnets"); break; + case "ByVrfId": - controller = controllers.vrf; + controller = ApiController.Vrf; identifiers.Add(VrfId!.Value.ToString()); identifiers.Add("subnets"); break; + case "ByVlanId": - controller = controllers.vlan; + controller = ApiController.Vlan; identifiers.Add(VlanId!.Value.ToString()); identifiers.Add("subnets"); - if (SectionId.HasValue) - { - identifiers.Add(SectionId.Value.ToString()); - } - else if (!string.IsNullOrEmpty(SectionName)) - { - var section2 = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { SectionName }) - .GetAwaiter().GetResult(); - if (section2 != null) - { - var sectionObj2 = section2 as Section; - identifiers.Add(sectionObj2!.Id.ToString()); - } - } + AddSectionIdentifier(identifiers); break; + case "ByVlanNumber": - controller = controllers.vlan; - var vlans = RequestHelper.InvokeRequest("GET", controllers.vlan, types.Vlan, null, null, new[] { "search", VlanNumber!.Value.ToString() }) - .GetAwaiter().GetResult(); - if (vlans == null) - { - throw new Exception("Cannot find Vlan!"); - } - var vlanList = vlans as System.Collections.IEnumerable; - Vlan? foundVlan = null; - if (vlanList != null) - { - foreach (var v in vlanList) - { - if (v is Vlan vlan) - { - if (VlanDomainId.HasValue && vlan.DomainId != VlanDomainId.Value) - continue; - if (foundVlan != null) - { - throw new Exception($"More than one vLan with {VlanNumber} number is present!"); - } - foundVlan = vlan; - } - } - } - if (foundVlan == null) - { - throw new Exception("Cannot find Vlan!"); - } - identifiers.Add(foundVlan.Id.ToString()); + controller = ApiController.Vlan; + var vlan = FindVlanByNumber(VlanNumber!.Value, VlanDomainId); + identifiers.Add(vlan.Id.ToString()); identifiers.Add("subnets"); - if (SectionId.HasValue) - { - identifiers.Add(SectionId.Value.ToString()); - } - else if (!string.IsNullOrEmpty(SectionName)) - { - var section3 = RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { SectionName }) - .GetAwaiter().GetResult(); - if (section3 != null) - { - var sectionObj3 = section3 as Section; - identifiers.Add(sectionObj3!.Id.ToString()); - } - } + AddSectionIdentifier(identifiers); break; } - var result = RequestHelper.InvokeRequest("GET", controller, types.Subnetwork, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", controller, ModelType.Subnetwork, null, null, identifiers.ToArray() + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { WriteError(new ErrorRecord(ex, "GetSubnetError", ErrorCategory.InvalidOperation, null)); } } + + private Section GetSectionByName(string name) + { + var section = RequestHelper.InvokeRequest( + "GET", ApiController.Sections, ModelType.Section, null, null, new[] { name } + ).GetAwaiter().GetResult() as Section; + + if (section == null) + { + throw new ItemNotFoundException($"Section '{name}' not found."); + } + + return section; + } + + private Vlan FindVlanByNumber(int number, int? domainId) + { + var vlans = RequestHelper.InvokeRequest( + "GET", ApiController.Vlan, ModelType.Vlan, null, null, + new[] { "search", number.ToString() } + ).GetAwaiter().GetResult(); + + if (vlans == null) + { + throw new ItemNotFoundException($"VLAN {number} not found."); + } + + Vlan? foundVlan = null; + + if (vlans is System.Collections.IEnumerable enumerable) + { + foreach (var v in enumerable) + { + if (v is Vlan vlan) + { + if (domainId.HasValue && vlan.DomainId != domainId.Value) + { + continue; + } + + if (foundVlan != null) + { + throw new InvalidOperationException( + $"Multiple VLANs with number {number} exist. Specify VlanDomainId to narrow the search."); + } + + foundVlan = vlan; + } + } + } + + if (foundVlan == null) + { + throw new ItemNotFoundException($"VLAN {number} not found."); + } + + return foundVlan; + } + + private void AddSectionIdentifier(List identifiers) + { + if (SectionId.HasValue) + { + identifiers.Add(SectionId.Value.ToString()); + } + else if (!string.IsNullOrEmpty(SectionName)) + { + var section = GetSectionByName(SectionName); + identifiers.Add(section.Id.ToString()); + } + } } diff --git a/classlib/Cmdlets/GetSubnetUsageCmdlet.cs b/classlib/Cmdlets/GetSubnetUsageCmdlet.cs index d4bd301..bedc68b 100644 --- a/classlib/Cmdlets/GetSubnetUsageCmdlet.cs +++ b/classlib/Cmdlets/GetSubnetUsageCmdlet.cs @@ -1,20 +1,23 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using System.Net; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves subnet usage statistics from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "SubnetUsage", DefaultParameterSetName = "ByID")] -public class GetSubnetUsageCmdlet : PSCmdlet +public class GetSubnetUsageCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByCIDR")] + ParameterSetName = "ByCIDR", + HelpMessage = "The subnet in CIDR notation.")] [ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")] [ValidateNotNullOrEmpty] public string? CIDR { get; set; } @@ -24,43 +27,60 @@ public class GetSubnetUsageCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The subnet ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } + [Parameter( + Mandatory = true, + ValueFromPipeline = true, + Position = 0, + ParameterSetName = "BySubnetObject", + HelpMessage = "The subnet object.")] + [ValidateNotNullOrEmpty] + public Subnetwork? SubnetObject { get; set; } + protected override void ProcessRecord() { try { - int subnetId; - if (ParameterSetName == "ByCIDR") - { - var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", CIDR! }) - .GetAwaiter().GetResult(); - if (subnet == null) - { - throw new Exception("Cannot find subnet!"); - } - var subnetObj = subnet as Subnetwork; - subnetId = subnetObj!.Id; - } - else - { - subnetId = Id!.Value; - } - + var subnetId = GetSubnetId(); var identifiers = new List { subnetId.ToString(), "usage" }; - var result = RequestHelper.InvokeRequest("GET", controllers.subnets, null, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); - if (result != null) - { - WriteObject(result); - } + var result = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, null, null, null, identifiers.ToArray() + ).GetAwaiter().GetResult(); + + WriteResult(result); } catch (Exception ex) { WriteError(new ErrorRecord(ex, "GetSubnetUsageError", ErrorCategory.InvalidOperation, null)); } } + + private int GetSubnetId() + { + switch (ParameterSetName) + { + case "ByCIDR": + var subnet = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, ModelType.Subnetwork, null, null, + new[] { "cidr", CIDR! } + ).GetAwaiter().GetResult() as Subnetwork; + + if (subnet == null) + { + throw new ItemNotFoundException($"Subnet '{CIDR}' not found."); + } + return subnet.Id; + + case "BySubnetObject": + return SubnetObject!.Id; + + default: + return Id!.Value; + } + } } diff --git a/classlib/Cmdlets/GetTagCmdlet.cs b/classlib/Cmdlets/GetTagCmdlet.cs index 1c0d3a7..cc9d1cd 100644 --- a/classlib/Cmdlets/GetTagCmdlet.cs +++ b/classlib/Cmdlets/GetTagCmdlet.cs @@ -1,20 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves tag information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Tag", DefaultParameterSetName = "NoParams")] [OutputType(typeof(Tag))] -public class GetTagCmdlet : PSCmdlet +public class GetTagCmdlet : BaseCmdlet { [Parameter( Mandatory = false, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The tag ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -23,7 +27,8 @@ public class GetTagCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByAddressObject")] + ParameterSetName = "ByAddressObject", + HelpMessage = "Get the tag associated with this address.")] [ValidateNotNullOrEmpty] public Address? AddressObject { get; set; } @@ -32,7 +37,8 @@ public class GetTagCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetObject")] + ParameterSetName = "BySubnetObject", + HelpMessage = "Get the tag associated with this subnet.")] [ValidateNotNullOrEmpty] public Subnetwork? SubnetObject { get; set; } @@ -50,6 +56,7 @@ public class GetTagCmdlet : PSCmdlet identifiers.Add(Id.Value.ToString()); } break; + case "ByAddressObject": if (AddressObject?.TagId > 0) { @@ -60,6 +67,7 @@ public class GetTagCmdlet : PSCmdlet return; } break; + case "BySubnetObject": if (SubnetObject?.TagId > 0) { @@ -72,23 +80,11 @@ public class GetTagCmdlet : PSCmdlet break; } - var result = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Tag, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", ApiController.Addresses, ModelType.Tag, null, null, identifiers.ToArray() + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { diff --git a/classlib/Cmdlets/GetVlanCmdlet.cs b/classlib/Cmdlets/GetVlanCmdlet.cs index 7425b19..06d9c82 100644 --- a/classlib/Cmdlets/GetVlanCmdlet.cs +++ b/classlib/Cmdlets/GetVlanCmdlet.cs @@ -1,20 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves VLAN information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Vlan", DefaultParameterSetName = "NoParams")] [OutputType(typeof(Vlan))] -public class GetVlanCmdlet : PSCmdlet +public class GetVlanCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The VLAN ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -23,7 +27,8 @@ public class GetVlanCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByNumber")] + ParameterSetName = "ByNumber", + HelpMessage = "The VLAN number.")] [ValidateNotNullOrEmpty] public int? Number { get; set; } @@ -32,7 +37,8 @@ public class GetVlanCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByL2Domain")] + ParameterSetName = "ByL2Domain", + HelpMessage = "The L2 domain ID to get VLANs from.")] [ValidateNotNullOrEmpty] public int? L2DomainId { get; set; } @@ -41,7 +47,8 @@ public class GetVlanCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetObject")] + ParameterSetName = "BySubnetObject", + HelpMessage = "Get the VLAN associated with this subnet.")] [ValidateNotNullOrEmpty] public Subnetwork? SubnetObject { get; set; } @@ -50,7 +57,8 @@ public class GetVlanCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByDomainObject")] + ParameterSetName = "ByDomainObject", + HelpMessage = "Get VLANs in this L2 domain.")] [ValidateNotNullOrEmpty] public Domain? DomainObject { get; set; } @@ -58,7 +66,7 @@ public class GetVlanCmdlet : PSCmdlet { try { - var controller = controllers.vlan; + var controller = ApiController.Vlan; var identifiers = new List(); switch (ParameterSetName) @@ -66,65 +74,44 @@ public class GetVlanCmdlet : PSCmdlet case "ByID": identifiers.Add(Id!.Value.ToString()); break; + case "ByNumber": identifiers.Add("search"); identifiers.Add(Number!.Value.ToString()); break; + case "ByL2Domain": - controller = controllers.l2domains; + controller = ApiController.L2Domains; identifiers.Add(L2DomainId!.Value.ToString()); - identifiers.Add(subcontrollers.vlans.ToString()); + identifiers.Add(ApiSubController.Vlans.ToString().ToLowerInvariant()); break; + case "BySubnetObject": - if (SubnetObject != null && SubnetObject.VlanId > 0) - { - identifiers.Add(SubnetObject.VlanId.ToString()); - } - else + if (SubnetObject == null || SubnetObject.VlanId <= 0) { return; } + identifiers.Add(SubnetObject.VlanId.ToString()); break; + case "ByDomainObject": if (DomainObject != null) { - var result = RequestHelper.InvokeRequest("GET", controllers.l2domains, types.Vlan, subcontrollers.vlans, null, new[] { DomainObject.Id.ToString() }) - .GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + var result = RequestHelper.InvokeRequest( + "GET", ApiController.L2Domains, ModelType.Vlan, ApiSubController.Vlans, + null, new[] { DomainObject.Id.ToString() } + ).GetAwaiter().GetResult(); + WriteResult(result); } return; } - var vlanResult = RequestHelper.InvokeRequest("GET", controller, types.Vlan, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null) - .GetAwaiter().GetResult(); + var vlanResult = RequestHelper.InvokeRequest( + "GET", controller, ModelType.Vlan, null, null, + identifiers.Count > 0 ? identifiers.ToArray() : null + ).GetAwaiter().GetResult(); - if (vlanResult != null) - { - if (vlanResult is System.Collections.IEnumerable enumerable && !(vlanResult is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(vlanResult); - } - } + WriteResult(vlanResult); } catch (Exception ex) { diff --git a/classlib/Cmdlets/GetVrfCmdlet.cs b/classlib/Cmdlets/GetVrfCmdlet.cs index 898cd80..755f25c 100644 --- a/classlib/Cmdlets/GetVrfCmdlet.cs +++ b/classlib/Cmdlets/GetVrfCmdlet.cs @@ -1,20 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Retrieves VRF information from phpIPAM. +/// [Cmdlet(VerbsCommon.Get, "Vrf", DefaultParameterSetName = "NoParams")] [OutputType(typeof(Vrf))] -public class GetVrfCmdlet : PSCmdlet +public class GetVrfCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByID")] + ParameterSetName = "ByID", + HelpMessage = "The VRF ID.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -23,28 +27,18 @@ public class GetVrfCmdlet : PSCmdlet try { var identifiers = new List(); + if (Id.HasValue) { identifiers.Add(Id.Value.ToString()); } - var result = RequestHelper.InvokeRequest("GET", controllers.vrf, types.Vrf, null, null, identifiers.Count > 0 ? identifiers.ToArray() : null) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "GET", ApiController.Vrf, ModelType.Vrf, null, null, + identifiers.Count > 0 ? identifiers.ToArray() : null + ).GetAwaiter().GetResult(); - if (result != null) - { - if (result is System.Collections.IEnumerable enumerable && !(result is string)) - { - foreach (var item in enumerable) - { - WriteObject(item); - } - } - else - { - WriteObject(result); - } - } + WriteResult(result); } catch (Exception ex) { diff --git a/classlib/Cmdlets/NewAddressCmdlet.cs b/classlib/Cmdlets/NewAddressCmdlet.cs index def3663..66ce858 100644 --- a/classlib/Cmdlets/NewAddressCmdlet.cs +++ b/classlib/Cmdlets/NewAddressCmdlet.cs @@ -1,21 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using System.Net; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Creates a new IP address entry in phpIPAM. +/// [Cmdlet(VerbsCommon.New, "Address", DefaultParameterSetName = "BySubnetId")] [OutputType(typeof(Address))] -public class NewAddressCmdlet : PSCmdlet +public class NewAddressCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetId")] + ParameterSetName = "BySubnetId", + HelpMessage = "The subnet ID to create the address in.")] [ValidateNotNullOrEmpty] public int? SubnetId { get; set; } @@ -24,7 +27,8 @@ public class NewAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "BySubnetObject")] + ParameterSetName = "BySubnetObject", + HelpMessage = "The subnet object to create the address in.")] [ValidateNotNullOrEmpty] public Subnetwork? SubnetObject { get; set; } @@ -32,166 +36,73 @@ public class NewAddressCmdlet : PSCmdlet Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 1)] + Position = 1, + HelpMessage = "The IP address to create.")] [ValidateNotNullOrEmpty] public string IP { get; set; } = string.Empty; - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 2)] + [Parameter(Mandatory = false, Position = 2, HelpMessage = "Mark this address as a gateway.")] public SwitchParameter Gateway { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 3)] + [Parameter(Mandatory = false, Position = 3, HelpMessage = "Description for the address.")] public string? Description { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 4)] + [Parameter(Mandatory = false, Position = 4, HelpMessage = "Hostname for the address.")] public string? Hostname { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 5)] + [Parameter(Mandatory = false, Position = 5, HelpMessage = "MAC address.")] public string? MAC { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 6)] + [Parameter(Mandatory = false, Position = 6, HelpMessage = "Owner of the address.")] public string? Owner { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 7)] + [Parameter(Mandatory = false, Position = 7, HelpMessage = "Tag ID for the address.")] public int? TagId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 8)] + [Parameter(Mandatory = false, Position = 8, HelpMessage = "Ignore PTR record generation.")] public SwitchParameter PTRIgnore { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 7)] + [Parameter(Mandatory = false, Position = 9, HelpMessage = "PTR record ID.")] public int? PTRId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 10)] + [Parameter(Mandatory = false, Position = 10, HelpMessage = "Note for the address.")] public string? Note { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 11)] + [Parameter(Mandatory = false, Position = 11, HelpMessage = "Exclude from ping scanning.")] public SwitchParameter ExcludePing { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 12)] + [Parameter(Mandatory = false, Position = 12, HelpMessage = "Associated device ID.")] public int? DeviceId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 13)] + [Parameter(Mandatory = false, Position = 13, HelpMessage = "Port information.")] public string? Port { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 14)] + [Parameter(Mandatory = false, Position = 14, HelpMessage = "Custom fields as a hashtable or PSObject.")] public object? CustomFields { get; set; } protected override void ProcessRecord() { try { - int actualSubnetId; - if (ParameterSetName == "BySubnetObject") - { - actualSubnetId = SubnetObject!.Id; - } - else - { - actualSubnetId = SubnetId!.Value; - } + var actualSubnetId = ParameterSetName == "BySubnetObject" + ? SubnetObject!.Id + : SubnetId!.Value; - var body = new Dictionary - { - { "subnetId", actualSubnetId }, - { "ip", IP } - }; + var body = BuildRequestBody(actualSubnetId); - if (Gateway.IsPresent) - body["is_gateway"] = "1"; - if (!string.IsNullOrEmpty(Description)) - body["description"] = Description; - if (!string.IsNullOrEmpty(Hostname)) - body["hostname"] = Hostname; - if (!string.IsNullOrEmpty(MAC)) - body["mac"] = MAC; - if (!string.IsNullOrEmpty(Owner)) - body["owner"] = Owner; - if (TagId.HasValue) - body["tag"] = TagId.Value; - if (PTRIgnore.IsPresent) - body["PTRignore"] = "1"; - if (PTRId.HasValue) - body["PTR"] = PTRId.Value; - if (!string.IsNullOrEmpty(Note)) - body["note"] = Note; - if (ExcludePing.IsPresent) - body["excludePing"] = "1"; - if (DeviceId.HasValue) - body["deviceId"] = DeviceId.Value; - if (!string.IsNullOrEmpty(Port)) - body["port"] = Port; - - if (CustomFields != null) - { - var customDict = ConvertCustomFields(CustomFields); - foreach (var kvp in customDict) - { - body[kvp.Key] = kvp.Value; - } - } - - var result = RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, body, null) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "POST", ApiController.Addresses, null, null, body + ).GetAwaiter().GetResult(); if (result != null) { - var address = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "search", IP }) - .GetAwaiter().GetResult(); - if (address != null) - { - WriteObject(address); - } + // Fetch the created address to return it + var address = RequestHelper.InvokeRequest( + "GET", ApiController.Addresses, ModelType.Address, null, null, + new[] { "search", IP } + ).GetAwaiter().GetResult(); + + WriteResult(address); } } catch (Exception ex) @@ -200,20 +111,32 @@ public class NewAddressCmdlet : PSCmdlet } } - private Dictionary ConvertCustomFields(object customFields) + private Dictionary BuildRequestBody(int subnetId) { - var dict = new Dictionary(); - if (customFields is PSObject psobj) + var body = new Dictionary { - foreach (var prop in psobj.Properties) - { - dict[prop.Name] = prop.Value ?? new object(); - } - } - else if (customFields is Dictionary dictObj) + ["subnetId"] = subnetId, + ["ip"] = IP + }; + + if (Gateway.IsPresent) body["is_gateway"] = "1"; + if (!string.IsNullOrEmpty(Description)) body["description"] = Description; + if (!string.IsNullOrEmpty(Hostname)) body["hostname"] = Hostname; + if (!string.IsNullOrEmpty(MAC)) body["mac"] = MAC; + if (!string.IsNullOrEmpty(Owner)) body["owner"] = Owner; + if (TagId.HasValue) body["tag"] = TagId.Value; + if (PTRIgnore.IsPresent) body["PTRignore"] = "1"; + if (PTRId.HasValue) body["PTR"] = PTRId.Value; + if (!string.IsNullOrEmpty(Note)) body["note"] = Note; + if (ExcludePing.IsPresent) body["excludePing"] = "1"; + if (DeviceId.HasValue) body["deviceId"] = DeviceId.Value; + if (!string.IsNullOrEmpty(Port)) body["port"] = Port; + + foreach (var kvp in ConvertCustomFields(CustomFields)) { - return dictObj; + body[kvp.Key] = kvp.Value; } - return dict; + + return body; } } diff --git a/classlib/Cmdlets/NewFirstFreeIPCmdlet.cs b/classlib/Cmdlets/NewFirstFreeIPCmdlet.cs index ca48fff..cecbd66 100644 --- a/classlib/Cmdlets/NewFirstFreeIPCmdlet.cs +++ b/classlib/Cmdlets/NewFirstFreeIPCmdlet.cs @@ -1,168 +1,85 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Creates a new address using the first available IP in a subnet. +/// [Cmdlet(VerbsCommon.New, "FirstFreeIP")] [OutputType(typeof(Address))] -public class NewFirstFreeIPCmdlet : PSCmdlet +public class NewFirstFreeIPCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 0)] + Position = 0, + HelpMessage = "The subnet ID to create the address in.")] [ValidateNotNullOrEmpty] public string SubnetId { get; set; } = string.Empty; - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 2)] + [Parameter(Mandatory = false, Position = 1, HelpMessage = "Mark this address as a gateway.")] public SwitchParameter Gateway { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 3)] + [Parameter(Mandatory = false, Position = 2, HelpMessage = "Description for the address.")] public string? Description { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 4)] + [Parameter(Mandatory = false, Position = 3, HelpMessage = "Hostname for the address.")] public string? Hostname { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 5)] + [Parameter(Mandatory = false, Position = 4, HelpMessage = "MAC address.")] public string? MAC { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 6)] + [Parameter(Mandatory = false, Position = 5, HelpMessage = "Owner of the address.")] public string? Owner { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 7)] + [Parameter(Mandatory = false, Position = 6, HelpMessage = "Tag ID for the address.")] public string? TagId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 8)] + [Parameter(Mandatory = false, Position = 7, HelpMessage = "Ignore PTR record generation.")] public SwitchParameter PTRIgnore { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 9)] + [Parameter(Mandatory = false, Position = 8, HelpMessage = "PTR record ID.")] public string? PTRId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 10)] + [Parameter(Mandatory = false, Position = 9, HelpMessage = "Note for the address.")] public string? Note { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 11)] + [Parameter(Mandatory = false, Position = 10, HelpMessage = "Exclude from ping scanning.")] public SwitchParameter ExcludePing { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 12)] + [Parameter(Mandatory = false, Position = 11, HelpMessage = "Associated device ID.")] public string? DeviceId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 13)] + [Parameter(Mandatory = false, Position = 12, HelpMessage = "Port information.")] public string? Port { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = false, HelpMessage = "Custom fields as a hashtable or PSObject.")] public object? CustomFields { get; set; } protected override void ProcessRecord() { try { - var identifiers = new List { "first_free" }; - var body = new Dictionary - { - { "subnetId", SubnetId } - }; + var identifiers = new[] { "first_free" }; + var body = BuildRequestBody(); - if (Gateway.IsPresent) - body["is_gateway"] = "1"; - if (!string.IsNullOrEmpty(Description)) - body["description"] = Description; - if (!string.IsNullOrEmpty(Hostname)) - body["hostname"] = Hostname; - if (!string.IsNullOrEmpty(MAC)) - body["mac"] = MAC; - if (!string.IsNullOrEmpty(Owner)) - body["owner"] = Owner; - if (!string.IsNullOrEmpty(TagId)) - body["tag"] = TagId; - if (PTRIgnore.IsPresent) - body["PTRignore"] = "1"; - if (!string.IsNullOrEmpty(PTRId)) - body["PTR"] = PTRId; - if (!string.IsNullOrEmpty(Note)) - body["note"] = Note; - if (ExcludePing.IsPresent) - body["excludePing"] = "1"; - if (!string.IsNullOrEmpty(DeviceId)) - body["deviceId"] = DeviceId; - if (!string.IsNullOrEmpty(Port)) - body["port"] = Port; - - if (CustomFields != null) - { - var customDict = ConvertCustomFields(CustomFields); - foreach (var kvp in customDict) - { - body[kvp.Key] = kvp.Value; - } - } - - var result = RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, body, identifiers.ToArray()) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "POST", ApiController.Addresses, null, null, body, identifiers + ).GetAwaiter().GetResult(); if (result != null) { var ip = result.ToString(); - var address = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "search", ip }) - .GetAwaiter().GetResult(); - if (address != null) - { - WriteObject(address); - } + var address = RequestHelper.InvokeRequest( + "GET", ApiController.Addresses, ModelType.Address, null, null, + new[] { "search", ip! } + ).GetAwaiter().GetResult(); + + WriteResult(address); } } catch (Exception ex) @@ -171,20 +88,31 @@ public class NewFirstFreeIPCmdlet : PSCmdlet } } - private Dictionary ConvertCustomFields(object customFields) + private Dictionary BuildRequestBody() { - var dict = new Dictionary(); - if (customFields is PSObject psobj) + var body = new Dictionary { - foreach (var prop in psobj.Properties) - { - dict[prop.Name] = prop.Value ?? new object(); - } - } - else if (customFields is Dictionary dictObj) + ["subnetId"] = SubnetId + }; + + if (Gateway.IsPresent) body["is_gateway"] = "1"; + if (!string.IsNullOrEmpty(Description)) body["description"] = Description; + if (!string.IsNullOrEmpty(Hostname)) body["hostname"] = Hostname; + if (!string.IsNullOrEmpty(MAC)) body["mac"] = MAC; + if (!string.IsNullOrEmpty(Owner)) body["owner"] = Owner; + if (!string.IsNullOrEmpty(TagId)) body["tag"] = TagId; + if (PTRIgnore.IsPresent) body["PTRignore"] = "1"; + if (!string.IsNullOrEmpty(PTRId)) body["PTR"] = PTRId; + if (!string.IsNullOrEmpty(Note)) body["note"] = Note; + if (ExcludePing.IsPresent) body["excludePing"] = "1"; + if (!string.IsNullOrEmpty(DeviceId)) body["deviceId"] = DeviceId; + if (!string.IsNullOrEmpty(Port)) body["port"] = Port; + + foreach (var kvp in ConvertCustomFields(CustomFields)) { - return dictObj; + body[kvp.Key] = kvp.Value; } - return dict; + + return body; } } diff --git a/classlib/Cmdlets/NewSessionCmdlet.cs b/classlib/Cmdlets/NewSessionCmdlet.cs index 2f89eb4..3fe98ba 100644 --- a/classlib/Cmdlets/NewSessionCmdlet.cs +++ b/classlib/Cmdlets/NewSessionCmdlet.cs @@ -1,18 +1,22 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Management.Automation; -using System.Threading.Tasks; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Creates a new phpIPAM API session. +/// [Cmdlet(VerbsCommon.New, "Session", DefaultParameterSetName = "Credentials")] -public class NewSessionCmdlet : PSCmdlet +[OutputType(typeof(Session))] +public class NewSessionCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 0)] + Position = 0, + HelpMessage = "The phpIPAM server URL (must start with http:// or https://).")] [ValidateNotNullOrEmpty] [ValidatePattern("^https?://")] public string URL { get; set; } = string.Empty; @@ -21,7 +25,8 @@ public class NewSessionCmdlet : PSCmdlet Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 1)] + Position = 1, + HelpMessage = "The API application ID configured in phpIPAM.")] [ValidateNotNullOrEmpty] public string AppID { get; set; } = string.Empty; @@ -30,7 +35,8 @@ public class NewSessionCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 2, - ParameterSetName = "Credentials")] + ParameterSetName = "Credentials", + HelpMessage = "The credentials (username and password) for authentication.")] [ValidateNotNullOrEmpty] public PSCredential? Credentials { get; set; } @@ -39,7 +45,8 @@ public class NewSessionCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 2, - ParameterSetName = "Token")] + ParameterSetName = "Token", + HelpMessage = "The static API token for authentication.")] [ValidateNotNullOrEmpty] public string? Token { get; set; } @@ -47,7 +54,8 @@ public class NewSessionCmdlet : PSCmdlet Mandatory = false, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 3)] + Position = 3, + HelpMessage = "If specified, SSL certificate errors will be ignored.")] public SwitchParameter IgnoreSSL { get; set; } protected override void ProcessRecord() @@ -55,9 +63,10 @@ public class NewSessionCmdlet : PSCmdlet try { Session session; + if (ParameterSetName == "Credentials" && Credentials != null) { - session = SessionManager.CreateSessionWithCredentials( + session = SessionManager.CreateSessionWithCredentialsAsync( URL, AppID, Credentials, @@ -70,12 +79,14 @@ public class NewSessionCmdlet : PSCmdlet } else { - throw new ArgumentException("Invalid parameter set"); + throw new ArgumentException("Invalid parameter set. Provide either Credentials or Token."); } + + WriteObject(session); } catch (Exception ex) { - WriteError(new ErrorRecord(ex, "NewSessionError", ErrorCategory.InvalidOperation, null)); + WriteError(new ErrorRecord(ex, "NewSessionError", ErrorCategory.AuthenticationError, null)); } } } diff --git a/classlib/Cmdlets/NewSubnetCmdlet.cs b/classlib/Cmdlets/NewSubnetCmdlet.cs index cf2d41b..a34f5c5 100644 --- a/classlib/Cmdlets/NewSubnetCmdlet.cs +++ b/classlib/Cmdlets/NewSubnetCmdlet.cs @@ -1,20 +1,23 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using System.Net; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Creates a new subnet in phpIPAM. +/// [Cmdlet(VerbsCommon.New, "Subnet")] [OutputType(typeof(Subnetwork))] -public class NewSubnetCmdlet : PSCmdlet +public class NewSubnetCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 0)] + Position = 0, + HelpMessage = "The subnet in CIDR notation (e.g., 192.168.1.0/24).")] [ValidatePattern(@"^\d+\.\d+\.\d+\.\d+/\d{1,2}$")] [ValidateNotNullOrEmpty] public string CIDR { get; set; } = string.Empty; @@ -23,187 +26,79 @@ public class NewSubnetCmdlet : PSCmdlet Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 1)] + Position = 1, + HelpMessage = "The section ID to create the subnet in.")] [ValidateNotNullOrEmpty] public int SectionId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 2)] + [Parameter(Mandatory = false, Position = 2, HelpMessage = "Description for the subnet.")] public string? Description { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 3)] + [Parameter(Mandatory = false, Position = 3, HelpMessage = "VLAN ID to associate.")] public int? VlanId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 4)] + [Parameter(Mandatory = false, Position = 4, HelpMessage = "VRF ID to associate.")] public int? VrfId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 5)] + [Parameter(Mandatory = false, Position = 5, HelpMessage = "Master subnet ID for hierarchy.")] public int? MasterSubnetId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 6)] + [Parameter(Mandatory = false, Position = 6, HelpMessage = "Nameserver ID.")] public int? NameserverId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 7)] + [Parameter(Mandatory = false, Position = 7, HelpMessage = "Show subnet name.")] public SwitchParameter ShowName { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 8)] + [Parameter(Mandatory = false, Position = 8, HelpMessage = "Enable recursive DNS.")] public SwitchParameter DNSRecursive { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 9)] + [Parameter(Mandatory = false, Position = 9, HelpMessage = "Enable DNS records.")] public SwitchParameter DNSRecords { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 10)] + [Parameter(Mandatory = false, Position = 10, HelpMessage = "Allow IP requests.")] public SwitchParameter AllowRequests { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 11)] + [Parameter(Mandatory = false, Position = 11, HelpMessage = "Scan agent ID.")] public int? ScanAgentId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 12)] + [Parameter(Mandatory = false, Position = 12, HelpMessage = "Enable subnet discovery.")] public SwitchParameter DiscoverSubnet { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 12)] + [Parameter(Mandatory = false, Position = 13, HelpMessage = "Mark subnet as full.")] public SwitchParameter IsFull { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 12)] + [Parameter(Mandatory = false, Position = 14, HelpMessage = "Tag ID for the subnet.")] public int? TagId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 13)] + [Parameter(Mandatory = false, Position = 15, HelpMessage = "Usage threshold percentage (1-100).")] [ValidateRange(1, 100)] public int? Threshold { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 14)] + [Parameter(Mandatory = false, Position = 16, HelpMessage = "Location ID.")] public int? LocationId { get; set; } - [Parameter( - Mandatory = false, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - Position = 15)] + [Parameter(Mandatory = false, HelpMessage = "Custom fields as a hashtable or PSObject.")] public object? CustomFields { get; set; } protected override void ProcessRecord() { try { - var parts = CIDR.Split('/'); - var body = new Dictionary - { - { "subnet", parts[0] }, - { "mask", parts[1] }, - { "sectionId", SectionId } - }; + var body = BuildRequestBody(); - if (!string.IsNullOrEmpty(Description)) - body["description"] = Description; - if (VlanId.HasValue) - body["vlanId"] = VlanId.Value; - if (VrfId.HasValue) - body["vrfId"] = VrfId.Value; - if (MasterSubnetId.HasValue) - body["masterSubnetId"] = MasterSubnetId.Value; - if (NameserverId.HasValue) - body["nameserverId"] = NameserverId.Value; - if (ShowName.IsPresent) - body["showName"] = "1"; - if (DNSRecursive.IsPresent) - body["DNSrecursive"] = "1"; - if (DNSRecords.IsPresent) - body["DNSrecords"] = "1"; - if (AllowRequests.IsPresent) - body["allowRequests"] = "1"; - if (ScanAgentId.HasValue) - body["scanAgent"] = ScanAgentId.Value; - if (DiscoverSubnet.IsPresent) - body["discoverSubnet"] = "1"; - if (IsFull.IsPresent) - body["isFull"] = "1"; - if (TagId.HasValue) - body["state"] = TagId.Value; - if (Threshold.HasValue) - body["threshold"] = Threshold.Value; - if (LocationId.HasValue) - body["location"] = LocationId.Value; - - if (CustomFields != null) - { - var customDict = ConvertCustomFields(CustomFields); - foreach (var kvp in customDict) - { - body[kvp.Key] = kvp.Value; - } - } - - var result = RequestHelper.InvokeRequest("POST", controllers.subnets, null, null, body, null) - .GetAwaiter().GetResult(); + var result = RequestHelper.InvokeRequest( + "POST", ApiController.Subnets, null, null, body + ).GetAwaiter().GetResult(); if (result != null) { // Get the created subnet - var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "cidr", CIDR }) - .GetAwaiter().GetResult(); - if (subnet != null) - { - WriteObject(subnet); - } + var subnet = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, ModelType.Subnetwork, null, null, + new[] { "cidr", CIDR } + ).GetAwaiter().GetResult(); + + WriteResult(subnet); } } catch (Exception ex) @@ -212,20 +107,37 @@ public class NewSubnetCmdlet : PSCmdlet } } - private Dictionary ConvertCustomFields(object customFields) + private Dictionary BuildRequestBody() { - var dict = new Dictionary(); - if (customFields is PSObject psobj) + var parts = CIDR.Split('/'); + var body = new Dictionary { - foreach (var prop in psobj.Properties) - { - dict[prop.Name] = prop.Value ?? new object(); - } - } - else if (customFields is Dictionary dictObj) + ["subnet"] = parts[0], + ["mask"] = parts[1], + ["sectionId"] = SectionId + }; + + if (!string.IsNullOrEmpty(Description)) body["description"] = Description; + if (VlanId.HasValue) body["vlanId"] = VlanId.Value; + if (VrfId.HasValue) body["vrfId"] = VrfId.Value; + if (MasterSubnetId.HasValue) body["masterSubnetId"] = MasterSubnetId.Value; + if (NameserverId.HasValue) body["nameserverId"] = NameserverId.Value; + if (ShowName.IsPresent) body["showName"] = "1"; + if (DNSRecursive.IsPresent) body["DNSrecursive"] = "1"; + if (DNSRecords.IsPresent) body["DNSrecords"] = "1"; + if (AllowRequests.IsPresent) body["allowRequests"] = "1"; + if (ScanAgentId.HasValue) body["scanAgent"] = ScanAgentId.Value; + if (DiscoverSubnet.IsPresent) body["discoverSubnet"] = "1"; + if (IsFull.IsPresent) body["isFull"] = "1"; + if (TagId.HasValue) body["state"] = TagId.Value; + if (Threshold.HasValue) body["threshold"] = Threshold.Value; + if (LocationId.HasValue) body["location"] = LocationId.Value; + + foreach (var kvp in ConvertCustomFields(CustomFields)) { - return dictObj; + body[kvp.Key] = kvp.Value; } - return dict; + + return body; } } diff --git a/classlib/Cmdlets/RemoveAddressCmdlet.cs b/classlib/Cmdlets/RemoveAddressCmdlet.cs index 744873b..f064814 100644 --- a/classlib/Cmdlets/RemoveAddressCmdlet.cs +++ b/classlib/Cmdlets/RemoveAddressCmdlet.cs @@ -1,27 +1,31 @@ namespace PS.IPAM.Cmdlets; + using System; -using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; -[Cmdlet(VerbsCommon.Remove, "Address", DefaultParameterSetName = "ByID")] -public class RemoveAddressCmdlet : PSCmdlet +/// +/// Removes an IP address entry from phpIPAM. +/// +[Cmdlet(VerbsCommon.Remove, "Address", DefaultParameterSetName = "ById", SupportsShouldProcess = true)] +public class RemoveAddressCmdlet : BaseCmdlet { - [Parameter( - Mandatory = true, - ValueFromPipeline = true, - Position = 0, - ParameterSetName = "ByID")] - [ValidateNotNullOrEmpty] - public int? Id { get; set; } - [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByAddressObject")] + ParameterSetName = "ById", + HelpMessage = "The address ID to remove.")] + [ValidateNotNullOrEmpty] + public int Id { get; set; } + + [Parameter( + Mandatory = true, + ValueFromPipeline = true, + Position = 0, + ParameterSetName = "ByAddressObject", + HelpMessage = "The address object to remove.")] [ValidateNotNullOrEmpty] public Address? AddressObject { get; set; } @@ -29,19 +33,17 @@ public class RemoveAddressCmdlet : PSCmdlet { try { - int addressId; - if (ParameterSetName == "ByID") - { - addressId = Id!.Value; - } - else - { - addressId = AddressObject!.Id; - } + var addressId = ParameterSetName == "ById" ? Id : AddressObject!.Id; + var identifiers = new[] { addressId.ToString() }; - var identifiers = new List { addressId.ToString() }; - RequestHelper.InvokeRequest("DELETE", controllers.addresses, null, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); + if (ShouldProcess($"Address ID: {addressId}", "Remove")) + { + RequestHelper.InvokeRequest( + "DELETE", ApiController.Addresses, null, null, null, identifiers + ).GetAwaiter().GetResult(); + + WriteVerbose($"Address {addressId} removed successfully."); + } } catch (Exception ex) { diff --git a/classlib/Cmdlets/SetAddressCmdlet.cs b/classlib/Cmdlets/SetAddressCmdlet.cs index 1a21035..d31c396 100644 --- a/classlib/Cmdlets/SetAddressCmdlet.cs +++ b/classlib/Cmdlets/SetAddressCmdlet.cs @@ -1,20 +1,24 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Updates an existing IP address entry in phpIPAM. +/// [Cmdlet(VerbsCommon.Set, "Address", DefaultParameterSetName = "ById")] [OutputType(typeof(Address))] -public class SetAddressCmdlet : PSCmdlet +public class SetAddressCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ById")] + ParameterSetName = "ById", + HelpMessage = "The address ID to update.")] [ValidateNotNullOrEmpty] public int? Id { get; set; } @@ -23,142 +27,72 @@ public class SetAddressCmdlet : PSCmdlet ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0, - ParameterSetName = "ByAddressObject")] + ParameterSetName = "ByAddressObject", + HelpMessage = "The address object to update.")] [ValidateNotNullOrEmpty] public Address? AddressObject { get; set; } - [Parameter( - Position = 1, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - ParameterSetName = "ById")] + [Parameter(Position = 1, HelpMessage = "Set as gateway address.")] public bool? Gateway { get; set; } - [Parameter( - Position = 2, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true, - ParameterSetName = "ById")] + [Parameter(Position = 2, HelpMessage = "Description for the address.")] public string? Description { get; set; } - [Parameter( - Mandatory = false, - Position = 3)] + [Parameter(Position = 3, HelpMessage = "Hostname for the address.")] public string? Hostname { get; set; } - [Parameter( - Mandatory = false, - Position = 4)] + [Parameter(Position = 4, HelpMessage = "MAC address.")] public string? MAC { get; set; } - [Parameter( - Mandatory = false, - Position = 5)] + [Parameter(Position = 5, HelpMessage = "Owner of the address.")] public string? Owner { get; set; } - [Parameter( - Mandatory = false, - Position = 6)] + [Parameter(Position = 6, HelpMessage = "Tag ID for the address.")] public int? TagId { get; set; } - [Parameter( - Mandatory = false, - Position = 7)] + [Parameter(Position = 7, HelpMessage = "Ignore PTR record generation.")] public bool? PTRIgnore { get; set; } - [Parameter( - Mandatory = false, - Position = 8)] + [Parameter(Position = 8, HelpMessage = "PTR record ID.")] public int? PTRId { get; set; } - [Parameter( - Mandatory = false, - Position = 9)] + [Parameter(Position = 9, HelpMessage = "Note for the address.")] public string? Note { get; set; } - [Parameter( - Mandatory = false, - Position = 10)] + [Parameter(Position = 10, HelpMessage = "Exclude from ping scanning.")] public bool? ExcludePing { get; set; } - [Parameter( - Mandatory = false, - Position = 11)] + [Parameter(Position = 11, HelpMessage = "Associated device ID.")] public int? DeviceId { get; set; } - [Parameter( - Mandatory = false, - Position = 12)] + [Parameter(Position = 12, HelpMessage = "Port information.")] public string? Port { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(HelpMessage = "Custom fields as a hashtable or PSObject.")] public object? CustomFields { get; set; } protected override void ProcessRecord() { try { - int addressId; - if (ParameterSetName == "ById") - { - addressId = Id!.Value; - } - else - { - addressId = AddressObject!.Id; - } - - var identifiers = new List { addressId.ToString() }; - var body = new Dictionary(); - - if (Gateway.HasValue) - body["is_gateway"] = Gateway.Value; - if (!string.IsNullOrEmpty(Description)) - body["description"] = Description; - if (!string.IsNullOrEmpty(Hostname)) - body["hostname"] = Hostname; - if (!string.IsNullOrEmpty(MAC)) - body["mac"] = MAC; - if (!string.IsNullOrEmpty(Owner)) - body["owner"] = Owner; - if (TagId.HasValue) - body["tag"] = TagId.Value; - if (PTRIgnore.HasValue) - body["PTRignore"] = PTRIgnore.Value; - if (PTRId.HasValue) - body["PTR"] = PTRId.Value; - if (!string.IsNullOrEmpty(Note)) - body["note"] = Note; - if (ExcludePing.HasValue) - body["excludePing"] = ExcludePing.Value; - if (DeviceId.HasValue) - body["deviceId"] = DeviceId.Value; - if (!string.IsNullOrEmpty(Port)) - body["port"] = Port; - - if (CustomFields != null) - { - var customDict = ConvertCustomFields(CustomFields); - foreach (var kvp in customDict) - { - body[kvp.Key] = kvp.Value; - } - } + var addressId = ParameterSetName == "ById" ? Id!.Value : AddressObject!.Id; + var identifiers = new[] { addressId.ToString() }; + var body = BuildRequestBody(); try { - RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, body, identifiers.ToArray()) - .GetAwaiter().GetResult(); + RequestHelper.InvokeRequest( + "PATCH", ApiController.Addresses, null, null, body, identifiers + ).GetAwaiter().GetResult(); } finally { - var address = RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); - if (address != null) - { - WriteObject(address); - } + // Always return the updated address + var address = RequestHelper.InvokeRequest( + "GET", ApiController.Addresses, ModelType.Address, null, null, identifiers + ).GetAwaiter().GetResult(); + + WriteResult(address); } } catch (Exception ex) @@ -167,20 +101,28 @@ public class SetAddressCmdlet : PSCmdlet } } - private Dictionary ConvertCustomFields(object customFields) + private Dictionary BuildRequestBody() { - var dict = new Dictionary(); - if (customFields is PSObject psobj) + var body = new Dictionary(); + + if (Gateway.HasValue) body["is_gateway"] = Gateway.Value; + if (!string.IsNullOrEmpty(Description)) body["description"] = Description; + if (!string.IsNullOrEmpty(Hostname)) body["hostname"] = Hostname; + if (!string.IsNullOrEmpty(MAC)) body["mac"] = MAC; + if (!string.IsNullOrEmpty(Owner)) body["owner"] = Owner; + if (TagId.HasValue) body["tag"] = TagId.Value; + if (PTRIgnore.HasValue) body["PTRignore"] = PTRIgnore.Value; + if (PTRId.HasValue) body["PTR"] = PTRId.Value; + if (!string.IsNullOrEmpty(Note)) body["note"] = Note; + if (ExcludePing.HasValue) body["excludePing"] = ExcludePing.Value; + if (DeviceId.HasValue) body["deviceId"] = DeviceId.Value; + if (!string.IsNullOrEmpty(Port)) body["port"] = Port; + + foreach (var kvp in ConvertCustomFields(CustomFields)) { - foreach (var prop in psobj.Properties) - { - dict[prop.Name] = prop.Value ?? new object(); - } + body[kvp.Key] = kvp.Value; } - else if (customFields is Dictionary dictObj) - { - return dictObj; - } - return dict; + + return body; } } diff --git a/classlib/Cmdlets/SetSubnetCmdlet.cs b/classlib/Cmdlets/SetSubnetCmdlet.cs index a43521c..12677c6 100644 --- a/classlib/Cmdlets/SetSubnetCmdlet.cs +++ b/classlib/Cmdlets/SetSubnetCmdlet.cs @@ -1,143 +1,91 @@ namespace PS.IPAM.Cmdlets; + using System; using System.Collections.Generic; using System.Management.Automation; -using PS.IPAM; using PS.IPAM.Helpers; +/// +/// Updates an existing subnet in phpIPAM. +/// [Cmdlet(VerbsCommon.Set, "Subnet")] [OutputType(typeof(Subnetwork))] -public class SetSubnetCmdlet : PSCmdlet +public class SetSubnetCmdlet : BaseCmdlet { [Parameter( Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, - Position = 0)] + Position = 0, + HelpMessage = "The subnet ID to update.")] [ValidateNotNullOrEmpty] public int Id { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Description for the subnet.")] public string? Description { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "VLAN ID to associate.")] public int? VlanId { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "VRF ID to associate.")] public int? VrfId { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Master subnet ID for hierarchy.")] public int? MasterSubnetId { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Nameserver ID.")] public int? NameserverId { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Show subnet name.")] public SwitchParameter ShowName { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Enable recursive DNS.")] public SwitchParameter DNSRecursive { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Enable DNS records.")] public SwitchParameter DNSRecords { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Allow IP requests.")] public SwitchParameter AllowRequests { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Scan agent ID.")] public int? ScanAgentId { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Enable subnet discovery.")] public SwitchParameter DiscoverSubnet { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Mark subnet as full.")] public SwitchParameter IsFull { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Tag ID for the subnet.")] public int? TagId { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Usage threshold percentage (1-100).")] [ValidateRange(1, 100)] public int? Threshold { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Location ID.")] public int? LocationId { get; set; } - [Parameter( - Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Custom fields as a hashtable or PSObject.")] public object? CustomFields { get; set; } protected override void ProcessRecord() { try { - var identifiers = new List { Id.ToString() }; - var body = new Dictionary(); + var identifiers = new[] { Id.ToString() }; + var body = BuildRequestBody(); - if (!string.IsNullOrEmpty(Description)) - body["description"] = Description; - if (VlanId.HasValue) - body["vlanId"] = VlanId.Value; - if (VrfId.HasValue) - body["vrfId"] = VrfId.Value; - if (MasterSubnetId.HasValue) - body["masterSubnetId"] = MasterSubnetId.Value; - if (NameserverId.HasValue) - body["nameserverId"] = NameserverId.Value; - if (ShowName.IsPresent) - body["showName"] = "1"; - if (DNSRecursive.IsPresent) - body["DNSrecursive"] = "1"; - if (DNSRecords.IsPresent) - body["DNSrecords"] = "1"; - if (AllowRequests.IsPresent) - body["allowRequests"] = "1"; - if (ScanAgentId.HasValue) - body["scanAgent"] = ScanAgentId.Value; - if (DiscoverSubnet.IsPresent) - body["discoverSubnet"] = "1"; - if (IsFull.IsPresent) - body["isFull"] = "1"; - if (TagId.HasValue) - body["state"] = TagId.Value; - if (Threshold.HasValue) - body["threshold"] = Threshold.Value; - if (LocationId.HasValue) - body["location"] = LocationId.Value; + RequestHelper.InvokeRequest( + "PATCH", ApiController.Subnets, null, null, body, identifiers + ).GetAwaiter().GetResult(); - if (CustomFields != null) - { - var customDict = ConvertCustomFields(CustomFields); - foreach (var kvp in customDict) - { - body[kvp.Key] = kvp.Value; - } - } + var subnet = RequestHelper.InvokeRequest( + "GET", ApiController.Subnets, ModelType.Subnetwork, null, null, identifiers + ).GetAwaiter().GetResult(); - RequestHelper.InvokeRequest("PATCH", controllers.subnets, null, null, body, identifiers.ToArray()) - .GetAwaiter().GetResult(); - - var subnet = RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, identifiers.ToArray()) - .GetAwaiter().GetResult(); - if (subnet != null) - { - WriteObject(subnet); - } + WriteResult(subnet); } catch (Exception ex) { @@ -145,20 +93,31 @@ public class SetSubnetCmdlet : PSCmdlet } } - private Dictionary ConvertCustomFields(object customFields) + private Dictionary BuildRequestBody() { - var dict = new Dictionary(); - if (customFields is PSObject psobj) + var body = new Dictionary(); + + if (!string.IsNullOrEmpty(Description)) body["description"] = Description; + if (VlanId.HasValue) body["vlanId"] = VlanId.Value; + if (VrfId.HasValue) body["vrfId"] = VrfId.Value; + if (MasterSubnetId.HasValue) body["masterSubnetId"] = MasterSubnetId.Value; + if (NameserverId.HasValue) body["nameserverId"] = NameserverId.Value; + if (ShowName.IsPresent) body["showName"] = "1"; + if (DNSRecursive.IsPresent) body["DNSrecursive"] = "1"; + if (DNSRecords.IsPresent) body["DNSrecords"] = "1"; + if (AllowRequests.IsPresent) body["allowRequests"] = "1"; + if (ScanAgentId.HasValue) body["scanAgent"] = ScanAgentId.Value; + if (DiscoverSubnet.IsPresent) body["discoverSubnet"] = "1"; + if (IsFull.IsPresent) body["isFull"] = "1"; + if (TagId.HasValue) body["state"] = TagId.Value; + if (Threshold.HasValue) body["threshold"] = Threshold.Value; + if (LocationId.HasValue) body["location"] = LocationId.Value; + + foreach (var kvp in ConvertCustomFields(CustomFields)) { - foreach (var prop in psobj.Properties) - { - dict[prop.Name] = prop.Value ?? new object(); - } + body[kvp.Key] = kvp.Value; } - else if (customFields is Dictionary dictObj) - { - return dictObj; - } - return dict; + + return body; } } diff --git a/classlib/Helpers/RequestHelper.cs b/classlib/Helpers/RequestHelper.cs index 8ee8178..5f846b3 100644 --- a/classlib/Helpers/RequestHelper.cs +++ b/classlib/Helpers/RequestHelper.cs @@ -1,4 +1,5 @@ namespace PS.IPAM.Helpers; + using System; using System.Collections.Generic; using System.Linq; @@ -9,135 +10,259 @@ using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using PS.IPAM; +/// +/// Helper class for making HTTP requests to the phpIPAM API. +/// public static class RequestHelper { - // Handler for testing - allows injecting a mock HTTP handler + /// + /// Handler for testing - allows injecting a mock HTTP handler. + /// public static HttpMessageHandler? TestHttpHandler { get; set; } - public static async Task InvokeRequest( - string method, - controllers controller, - types? type = null, - subcontrollers? subController = null, + /// + /// Invokes an HTTP request to the phpIPAM API. + /// + /// The HTTP method (GET, POST, PATCH, DELETE). + /// The API controller to call. + /// The expected model type for response conversion. + /// Optional sub-controller for nested endpoints. + /// Optional request body parameters. + /// Optional path identifiers. + /// Whether to ignore SSL certificate errors. + /// The deserialized response data, or null if not found. + public static async Task InvokeRequestAsync( + HttpMethod method, + ApiController controller, + ModelType? modelType = null, + ApiSubController? subController = null, object? parameters = null, string[]? identifiers = null, bool ignoreSsl = false) { - var tokenStatus = SessionManager.TestSession(); - if (tokenStatus == "NoToken") + EnsureValidSession(); + + var session = SessionManager.CurrentSession!; + var uri = BuildUri(session, controller, subController, identifiers); + + using var client = SessionManager.CreateHttpClient(ignoreSsl, TestHttpHandler); + ConfigureClient(client, session); + + var response = await SendRequestAsync(client, method, uri, parameters); + + if (response == null) { - throw new Exception("No session available!"); + return null; } - if (tokenStatus == "Expired") + var responseContent = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) { - await UpdateSession(); + if (response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return null; + } + throw new HttpRequestException($"Request failed with status {response.StatusCode}"); } + if (string.IsNullOrEmpty(responseContent)) + { + return null; + } + + return ParseResponse(responseContent, modelType); + } + + /// + /// Overload for backward compatibility using string method names. + /// + public static Task InvokeRequest( + string method, + ApiController controller, + ModelType? modelType = null, + ApiSubController? subController = null, + object? parameters = null, + string[]? identifiers = null, + bool ignoreSsl = false) + { + var httpMethod = method.ToUpperInvariant() switch + { + "GET" => HttpMethod.Get, + "POST" => HttpMethod.Post, + "PATCH" => new HttpMethod("PATCH"), + "DELETE" => HttpMethod.Delete, + "PUT" => HttpMethod.Put, + _ => throw new ArgumentException($"Unsupported HTTP method: {method}", nameof(method)) + }; + + return InvokeRequestAsync(httpMethod, controller, modelType, subController, parameters, identifiers, ignoreSsl); + } + + /// + /// Refreshes an expired session. + /// + public static async Task RefreshSessionAsync() + { var session = SessionManager.CurrentSession; if (session == null) { - throw new Exception("No session available!"); + throw new InvalidOperationException("No session available!"); } - var uri = $"{session.URL}/api/{session.AppID}/{controller}"; - if (subController != null) + var status = SessionManager.GetSessionStatus(); + if (status == SessionStatus.Valid) { - uri += $"/{subController}"; + // Just refresh the token + await InvokeRequestAsync(new HttpMethod("PATCH"), ApiController.User); + return; } - if (identifiers != null && identifiers.Length > 0) + + if (status == SessionStatus.Expired && session.Credentials is PSCredential creds) + { + await SessionManager.CreateSessionWithCredentialsAsync( + session.URL, + session.AppID, + creds, + false + ); + } + } + + private static void EnsureValidSession() + { + var status = SessionManager.GetSessionStatus(); + + switch (status) + { + case SessionStatus.NoSession: + throw new InvalidOperationException("No session available!"); + + case SessionStatus.Expired: + RefreshSessionAsync().GetAwaiter().GetResult(); + break; + } + } + + private static string BuildUri( + Session session, + ApiController controller, + ApiSubController? subController, + string[]? identifiers) + { + var controllerName = GetControllerName(controller); + var uri = $"{session.URL}/api/{session.AppID}/{controllerName}"; + + if (subController.HasValue) + { + uri += $"/{GetSubControllerName(subController.Value)}"; + } + + if (identifiers is { Length: > 0 }) { uri += $"/{string.Join("/", identifiers)}/"; } - using var client = SessionManager.CreateHttpClient(ignoreSsl, TestHttpHandler); + return uri; + } + + private static string GetControllerName(ApiController controller) => controller switch + { + ApiController.User => "user", + ApiController.Vlan => "vlan", + ApiController.Subnets => "subnets", + ApiController.Addresses => "addresses", + ApiController.Sections => "sections", + ApiController.Vrf => "vrf", + ApiController.L2Domains => "l2domains", + ApiController.Tools => "tools", + _ => throw new ArgumentOutOfRangeException(nameof(controller)) + }; + + private static string GetSubControllerName(ApiSubController subController) => subController switch + { + ApiSubController.Nameservers => "nameservers", + ApiSubController.Tags => "tags", + ApiSubController.Devices => "devices", + ApiSubController.DeviceTypes => "device_types", + ApiSubController.Vlans => "vlans", + ApiSubController.Vrfs => "vrfs", + ApiSubController.ScanAgents => "scanagents", + ApiSubController.Locations => "locations", + ApiSubController.Nat => "nat", + ApiSubController.Racks => "racks", + _ => throw new ArgumentOutOfRangeException(nameof(subController)) + }; + + private static void ConfigureClient(HttpClient client, Session session) + { client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); switch (session.AuthType) { - case AuthType.credentials: + case AuthType.Credentials: client.DefaultRequestHeaders.Add("token", session.Token); break; - case AuthType.token: + case AuthType.Token: client.DefaultRequestHeaders.Add("phpipam-token", session.Token); break; } + } - HttpResponseMessage? response = null; + private static async Task SendRequestAsync( + HttpClient client, + HttpMethod method, + string uri, + object? parameters) + { try { - if (method == "GET") + if (method == HttpMethod.Get) { - response = await client.GetAsync(uri); - } - else if (method == "POST") - { - var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}"; - var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); - response = await client.PostAsync(uri, content); - } - else if (method == "PATCH") - { - var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}"; - var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); - var request = new HttpRequestMessage(new HttpMethod("PATCH"), uri) - { - Content = content - }; - response = await client.SendAsync(request); - } - else if (method == "DELETE") - { - response = await client.DeleteAsync(uri); + return await client.GetAsync(uri); } - if (response == null) + if (method == HttpMethod.Delete) { - return null; + return await client.DeleteAsync(uri); } - var responseContent = await response.Content.ReadAsStringAsync(); + var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}"; + var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); - if (!response.IsSuccessStatusCode) + if (method == HttpMethod.Post) { - if (response.StatusCode == System.Net.HttpStatusCode.NotFound) - { - return null; - } - throw new HttpRequestException($"Request failed with status {response.StatusCode}"); + return await client.PostAsync(uri, content); } - if (string.IsNullOrEmpty(responseContent)) - { - return null; - } - - var jsonResponse = JsonConvert.DeserializeObject(responseContent); - if (jsonResponse == null) - { - return null; - } - - if (type.HasValue) - { - return ConvertToTypedObjects(jsonResponse, type.Value); - } - - return jsonResponse.data; + // PATCH, PUT, etc. + var request = new HttpRequestMessage(method, uri) { Content = content }; + return await client.SendAsync(request); } - catch (HttpRequestException ex) + catch (HttpRequestException ex) when (ex.Message.Contains("404")) { - if (ex.Message.Contains("404")) - { - return null; - } - throw; + return null; } } - private static object? ConvertToTypedObjects(dynamic jsonResponse, types type) + private static object? ParseResponse(string responseContent, ModelType? modelType) + { + var jsonResponse = JsonConvert.DeserializeObject(responseContent); + if (jsonResponse == null) + { + return null; + } + + if (!modelType.HasValue) + { + return jsonResponse.data; + } + + return ConvertToTypedObjects(jsonResponse, modelType.Value); + } + + private static object? ConvertToTypedObjects(dynamic jsonResponse, ModelType modelType) { if (jsonResponse?.data == null) { @@ -147,228 +272,170 @@ public static class RequestHelper var data = jsonResponse.data; if (data is JArray array) { - return array.Select(item => ConvertSingleObject(item, type)).ToList(); - } - else - { - return ConvertSingleObject(data, type); + return array.Select(item => ConvertSingleObject(item, modelType)).ToList(); } + + return ConvertSingleObject(data, modelType); } - private static object? ConvertSingleObject(dynamic item, types type) + private static object? ConvertSingleObject(dynamic item, ModelType modelType) { - var jobject = item as JObject; - if (jobject == null) + if (item is not JObject jobject) { return null; } + var customFields = ExtractCustomFields(jobject); + + return modelType switch + { + ModelType.Address => CreateAddress(jobject, customFields), + ModelType.Vlan => CreateVlan(jobject, customFields), + ModelType.Subnetwork => CreateSubnetwork(jobject, customFields), + ModelType.Vrf => CreateVrf(jobject, customFields), + ModelType.Section => CreateSection(jobject), + ModelType.Tag => CreateTag(jobject), + ModelType.Nameserver => CreateNameserver(jobject), + ModelType.Domain => CreateDomain(jobject), + _ => jobject.ToObject() + }; + } + + private static Dictionary? ExtractCustomFields(JObject jobject) + { var customFields = new Dictionary(); - foreach (var prop in jobject.Properties()) + foreach (var prop in jobject.Properties().Where(p => p.Name.StartsWith("custom_"))) { - if (prop.Name.StartsWith("custom_")) - { - customFields[prop.Name] = prop.Value?.ToObject() ?? new object(); - } - } - - switch (type) - { - case types.Address: - return CreateAddress(jobject, customFields); - case types.Vlan: - return CreateVlan(jobject, customFields); - case types.Subnetwork: - return CreateSubnetwork(jobject, customFields); - case types.Vrf: - return CreateVrf(jobject, customFields); - case types.Section: - return CreateSection(jobject); - case types.Tag: - return CreateTag(jobject); - case types.Nameserver: - return CreateNameserver(jobject); - case types.Domain: - return CreateDomain(jobject); - default: - return jobject.ToObject(); + customFields[prop.Name] = prop.Value?.ToObject() ?? string.Empty; } + return customFields.Count > 0 ? customFields : null; } - private static Address CreateAddress(JObject jobject, Dictionary customFields) - { - return new Address( - jobject["id"]?.ToObject() ?? 0, - jobject["subnetId"]?.ToObject() ?? 0, - jobject["ip"]?.ToString() ?? "", - jobject["is_gateway"]?.ToObject() ?? false, - jobject["description"]?.ToString() ?? "", - jobject["hostname"]?.ToString() ?? "", - jobject["mac"]?.ToString() ?? "", - jobject["owner"]?.ToString() ?? "", - jobject["tag"]?.ToObject() ?? 0, - jobject["deviceId"]?.ToObject() ?? 0, - jobject["location"]?.ToString() ?? "", - jobject["port"]?.ToString() ?? "", - jobject["note"]?.ToString() ?? "", - jobject["lastSeen"]?.ToObject(), - jobject["excludePing"]?.ToObject() ?? false, - jobject["PTRignore"]?.ToObject() ?? false, - jobject["PTR"]?.ToObject() ?? 0, - jobject["firewallAddressObject"]?.ToString() ?? "", - jobject["editDate"]?.ToObject(), - jobject["customer_id"]?.ToObject() ?? 0, - customFields.Count > 0 ? customFields : null - ); - } + #region Model Factory Methods - private static Vlan CreateVlan(JObject jobject, Dictionary customFields) - { - return new Vlan( - jobject["vlanId"]?.ToObject() ?? 0, - jobject["domainId"]?.ToObject() ?? 0, - jobject["name"]?.ToString() ?? "", - jobject["number"]?.ToObject() ?? 0, - jobject["description"]?.ToString() ?? "", - jobject["editDate"]?.ToObject(), - jobject["customer_id"]?.ToObject() ?? 0, - customFields.Count > 0 ? customFields : null - ); - } + private static Address CreateAddress(JObject obj, Dictionary? customFields) => new( + obj["id"]?.ToObject() ?? 0, + obj["subnetId"]?.ToObject() ?? 0, + obj["ip"]?.ToString() ?? "", + obj["is_gateway"]?.ToObject() ?? false, + obj["description"]?.ToString() ?? "", + obj["hostname"]?.ToString() ?? "", + obj["mac"]?.ToString() ?? "", + obj["owner"]?.ToString() ?? "", + obj["tag"]?.ToObject() ?? 0, + obj["deviceId"]?.ToObject() ?? 0, + obj["location"]?.ToString() ?? "", + obj["port"]?.ToString() ?? "", + obj["note"]?.ToString() ?? "", + obj["lastSeen"]?.ToObject(), + obj["excludePing"]?.ToObject() ?? false, + obj["PTRignore"]?.ToObject() ?? false, + obj["PTR"]?.ToObject() ?? 0, + obj["firewallAddressObject"]?.ToString() ?? "", + obj["editDate"]?.ToObject(), + obj["customer_id"]?.ToObject() ?? 0, + customFields + ); - private static Subnetwork CreateSubnetwork(JObject jobject, Dictionary customFields) - { - var props = jobject.Properties().ToList(); - return new Subnetwork( - jobject["id"]?.ToObject() ?? 0, - jobject["subnet"]?.ToString() ?? "", - jobject["mask"]?.ToObject() ?? 0, - jobject["sectionId"]?.ToObject() ?? 0, - jobject["description"]?.ToString() ?? "", - jobject["linked_subnet"]?.ToString() ?? "", - jobject["firewallAddressObject"]?.ToString() ?? "", - jobject["vrfId"]?.ToObject() ?? 0, - jobject["masterSubnetId"]?.ToObject() ?? 0, - jobject["allowRequests"]?.ToObject() ?? false, - jobject["vlanId"]?.ToObject() ?? 0, - jobject["showName"]?.ToObject() ?? false, - jobject["deviceId"]?.ToObject() ?? 0, - jobject["permissions"]?.ToString() ?? "", - jobject["pingSubnet"]?.ToObject() ?? false, - jobject["discoverSubnet"]?.ToObject() ?? false, - jobject["resolveDNS"]?.ToObject() ?? false, - jobject["DNSrecursive"]?.ToObject() ?? false, - jobject["DNSrecords"]?.ToObject() ?? false, - jobject["nameserverId"]?.ToObject() ?? 0, - jobject["scanAgent"]?.ToObject() ?? false, - jobject["isFolder"]?.ToObject() ?? false, - jobject["isFull"]?.ToObject() ?? false, - jobject["isPool"]?.ToObject() ?? false, - jobject["state"]?.ToObject() ?? 0, - jobject["threshold"]?.ToObject() ?? 0, - jobject["location"]?.ToObject() ?? 0, - jobject["editDate"]?.ToObject(), - jobject["lastScan"]?.ToObject(), - jobject["lastDiscovery"]?.ToObject(), - jobject["calculation"]?.ToObject() ?? new object(), - customFields.Count > 0 ? customFields : null - ); - } + private static Vlan CreateVlan(JObject obj, Dictionary? customFields) => new( + obj["vlanId"]?.ToObject() ?? 0, + obj["domainId"]?.ToObject() ?? 0, + obj["name"]?.ToString() ?? "", + obj["number"]?.ToObject() ?? 0, + obj["description"]?.ToString() ?? "", + obj["editDate"]?.ToObject(), + obj["customer_id"]?.ToObject() ?? 0, + customFields + ); - private static Vrf CreateVrf(JObject jobject, Dictionary customFields) - { - return new Vrf( - jobject["id"]?.ToObject() ?? 0, - jobject["name"]?.ToString() ?? "", - jobject["rd"]?.ToString() ?? "", - jobject["description"]?.ToString() ?? "", - jobject["sections"]?.ToString() ?? "", - jobject["editDate"]?.ToObject(), - customFields.Count > 0 ? customFields : null - ); - } + private static Subnetwork CreateSubnetwork(JObject obj, Dictionary? customFields) => new( + obj["id"]?.ToObject() ?? 0, + obj["subnet"]?.ToString() ?? "", + obj["mask"]?.ToObject() ?? 0, + obj["sectionId"]?.ToObject() ?? 0, + obj["description"]?.ToString() ?? "", + obj["linked_subnet"]?.ToString() ?? "", + obj["firewallAddressObject"]?.ToString() ?? "", + obj["vrfId"]?.ToObject() ?? 0, + obj["masterSubnetId"]?.ToObject() ?? 0, + obj["allowRequests"]?.ToObject() ?? false, + obj["vlanId"]?.ToObject() ?? 0, + obj["showName"]?.ToObject() ?? false, + obj["deviceId"]?.ToObject() ?? 0, + obj["permissions"]?.ToString() ?? "", + obj["pingSubnet"]?.ToObject() ?? false, + obj["discoverSubnet"]?.ToObject() ?? false, + obj["resolveDNS"]?.ToObject() ?? false, + obj["DNSrecursive"]?.ToObject() ?? false, + obj["DNSrecords"]?.ToObject() ?? false, + obj["nameserverId"]?.ToObject() ?? 0, + obj["scanAgent"]?.ToObject() ?? false, + obj["isFolder"]?.ToObject() ?? false, + obj["isFull"]?.ToObject() ?? false, + obj["isPool"]?.ToObject() ?? false, + obj["state"]?.ToObject() ?? 0, + obj["threshold"]?.ToObject() ?? 0, + obj["location"]?.ToObject() ?? 0, + obj["editDate"]?.ToObject(), + obj["lastScan"]?.ToObject(), + obj["lastDiscovery"]?.ToObject(), + obj["calculation"]?.ToObject() ?? new object(), + customFields + ); - private static Section CreateSection(JObject jobject) - { - return new Section( - jobject["id"]?.ToObject() ?? 0, - jobject["name"]?.ToString() ?? "", - jobject["description"]?.ToString() ?? "", - jobject["masterSection"]?.ToObject() ?? 0, - jobject["permissions"]?.ToString() ?? "", - jobject["strictMode"]?.ToObject() ?? false, - jobject["subnetOrdering"]?.ToString() ?? "", - jobject["order"]?.ToObject() ?? 0, - jobject["editDate"]?.ToObject(), - jobject["showSubnet"]?.ToObject() ?? false, - jobject["showVlan"]?.ToObject() ?? false, - jobject["showVRF"]?.ToObject() ?? false, - jobject["showSupernetOnly"]?.ToObject() ?? false, - jobject["DNS"]?.ToObject() ?? 0 - ); - } + private static Vrf CreateVrf(JObject obj, Dictionary? customFields) => new( + obj["id"]?.ToObject() ?? 0, + obj["name"]?.ToString() ?? "", + obj["rd"]?.ToString() ?? "", + obj["description"]?.ToString() ?? "", + obj["sections"]?.ToString() ?? "", + obj["editDate"]?.ToObject(), + customFields + ); - private static Tag CreateTag(JObject jobject) - { - return new Tag( - jobject["id"]?.ToObject() ?? 0, - jobject["type"]?.ToString() ?? "", - jobject["showtag"]?.ToObject() ?? false, - jobject["bgcolor"]?.ToString() ?? "", - jobject["fgcolor"]?.ToString() ?? "", - jobject["compress"]?.ToString() ?? "", - jobject["locked"]?.ToString() ?? "", - jobject["updateTag"]?.ToObject() ?? false - ); - } + private static Section CreateSection(JObject obj) => new( + obj["id"]?.ToObject() ?? 0, + obj["name"]?.ToString() ?? "", + obj["description"]?.ToString() ?? "", + obj["masterSection"]?.ToObject() ?? 0, + obj["permissions"]?.ToString() ?? "", + obj["strictMode"]?.ToObject() ?? false, + obj["subnetOrdering"]?.ToString() ?? "", + obj["order"]?.ToObject() ?? 0, + obj["editDate"]?.ToObject(), + obj["showSubnet"]?.ToObject() ?? false, + obj["showVlan"]?.ToObject() ?? false, + obj["showVRF"]?.ToObject() ?? false, + obj["showSupernetOnly"]?.ToObject() ?? false, + obj["DNS"]?.ToObject() ?? 0 + ); - private static Nameserver CreateNameserver(JObject jobject) - { - return new Nameserver( - jobject["id"]?.ToObject() ?? 0, - jobject["name"]?.ToString() ?? "", - jobject["nameservers"]?.ToString() ?? "", - jobject["description"]?.ToString() ?? "", - jobject["permissions"]?.ToString() ?? "", - jobject["editDate"]?.ToObject() - ); - } + private static Tag CreateTag(JObject obj) => new( + obj["id"]?.ToObject() ?? 0, + obj["type"]?.ToString() ?? "", + obj["showtag"]?.ToObject() ?? false, + obj["bgcolor"]?.ToString() ?? "", + obj["fgcolor"]?.ToString() ?? "", + obj["compress"]?.ToString() ?? "", + obj["locked"]?.ToString() ?? "", + obj["updateTag"]?.ToObject() ?? false + ); - private static Domain CreateDomain(JObject jobject) - { - return new Domain( - jobject["id"]?.ToObject() ?? 0, - jobject["name"]?.ToString() ?? "", - jobject["description"]?.ToString() ?? "", - jobject["sections"]?.ToString() ?? "" - ); - } + private static Nameserver CreateNameserver(JObject obj) => new( + obj["id"]?.ToObject() ?? 0, + obj["name"]?.ToString() ?? "", + obj["nameservers"]?.ToString() ?? "", + obj["description"]?.ToString() ?? "", + obj["permissions"]?.ToString() ?? "", + obj["editDate"]?.ToObject() + ); - private static async Task UpdateSession() - { - var session = SessionManager.CurrentSession; - if (session == null) - { - throw new Exception("No session available!"); - } + private static Domain CreateDomain(JObject obj) => new( + obj["id"]?.ToObject() ?? 0, + obj["name"]?.ToString() ?? "", + obj["description"]?.ToString() ?? "", + obj["sections"]?.ToString() ?? "" + ); - var tokenStatus = SessionManager.TestSession(); - if (tokenStatus == "Valid") - { - // Just refresh the token - var result = await InvokeRequest("PATCH", controllers.user, null, null, null, null); - // Token refresh doesn't return expires in the same format, so we'll skip updating expires - return; - } - - if (tokenStatus == "Expired" && session.Credentials is PSCredential creds) - { - await SessionManager.CreateSessionWithCredentials( - session.URL, - session.AppID, - creds, - false - ); - } - } + #endregion } diff --git a/classlib/Helpers/SessionManager.cs b/classlib/Helpers/SessionManager.cs index 4df215b..ff5c045 100644 --- a/classlib/Helpers/SessionManager.cs +++ b/classlib/Helpers/SessionManager.cs @@ -1,44 +1,60 @@ namespace PS.IPAM.Helpers; + using System; using System.Management.Automation; using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; -using PS.IPAM; +/// +/// Manages phpIPAM API sessions including creation, validation, and lifecycle. +/// public static class SessionManager { private static Session? _currentSession; + /// + /// Gets or sets the current active session. + /// public static Session? CurrentSession { get => _currentSession; set => _currentSession = value; } - public static string TestSession() + /// + /// Tests the current session status. + /// + /// The session status indicating validity or issues. + public static SessionStatus GetSessionStatus() { if (_currentSession == null) { - return "NoToken"; + return SessionStatus.NoSession; } if (_currentSession.Expires == null) { - return "Valid"; + return SessionStatus.Valid; } - if (_currentSession.Expires < DateTime.Now) - { - return "Expired"; - } - - return "Valid"; + return _currentSession.Expires < DateTime.Now + ? SessionStatus.Expired + : SessionStatus.Valid; } - public static async Task CreateSessionWithCredentials( + /// + /// Creates a new session using username/password credentials. + /// + /// The phpIPAM server URL. + /// The API application ID. + /// The PowerShell credential object. + /// Whether to ignore SSL certificate errors. + /// The created session. + public static async Task CreateSessionWithCredentialsAsync( string url, string appId, PSCredential credentials, @@ -46,7 +62,7 @@ public static class SessionManager { var uri = $"{url}/api/{appId}/user"; var auth = Convert.ToBase64String( - Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPassword(credentials)}")); + Encoding.UTF8.GetBytes($"{credentials.UserName}:{GetPasswordString(credentials)}")); using var client = CreateHttpClient(ignoreSsl); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); @@ -58,14 +74,15 @@ public static class SessionManager if (jsonResponse?.success != true) { - throw new Exception(jsonResponse?.error?.ToString() ?? "Failed to create session"); + throw new InvalidOperationException( + jsonResponse?.error?.ToString() ?? "Failed to create session"); } var token = jsonResponse.data.token.ToString(); var expires = DateTime.Parse(jsonResponse.data.expires.ToString()); _currentSession = new Session( - AuthType.credentials, + AuthType.Credentials, token, appId, url, @@ -76,13 +93,17 @@ public static class SessionManager return _currentSession; } - public static Session CreateSessionWithToken( - string url, - string appId, - string token) + /// + /// Creates a new session using a static API token. + /// + /// The phpIPAM server URL. + /// The API application ID. + /// The API token. + /// The created session. + public static Session CreateSessionWithToken(string url, string appId, string token) { _currentSession = new Session( - AuthType.token, + AuthType.Token, token, appId, url, @@ -93,24 +114,20 @@ public static class SessionManager return _currentSession; } + /// + /// Closes the current session and clears session data. + /// public static void CloseSession() { _currentSession = null; } - private static string GetPassword(PSCredential credential) - { - var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(credential.Password); - try - { - return System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr); - } - finally - { - System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr); - } - } - + /// + /// Creates an HttpClient with optional SSL bypass. + /// + /// Whether to ignore SSL certificate errors. + /// Optional custom message handler for testing. + /// A configured HttpClient instance. public static HttpClient CreateHttpClient(bool ignoreSsl = false, HttpMessageHandler? handler = null) { if (handler != null) @@ -122,10 +139,27 @@ public static class SessionManager { var sslHandler = new HttpClientHandler { - ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true + ServerCertificateCustomValidationCallback = (_, _, _, _) => true }; return new HttpClient(sslHandler); } + return new HttpClient(); } + + /// + /// Extracts the plain text password from a PSCredential object. + /// + private static string GetPasswordString(PSCredential credential) + { + var ptr = Marshal.SecureStringToBSTR(credential.Password); + try + { + return Marshal.PtrToStringBSTR(ptr); + } + finally + { + Marshal.ZeroFreeBSTR(ptr); + } + } } diff --git a/classlib/IsExternalInit.cs b/classlib/IsExternalInit.cs new file mode 100644 index 0000000..85ba17f --- /dev/null +++ b/classlib/IsExternalInit.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// This file provides the IsExternalInit type required for using C# 9 records +// in projects targeting .NET Standard 2.1 or earlier. + +#if !NET5_0_OR_GREATER + +namespace System.Runtime.CompilerServices +{ + using System.ComponentModel; + + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} + +#endif diff --git a/classlib/class/address.cs b/classlib/class/address.cs index d2044c9..80b3d56 100644 --- a/classlib/class/address.cs +++ b/classlib/class/address.cs @@ -1,77 +1,35 @@ -namespace PS.IPAM; +namespace PS.IPAM; + using System; +using System.Collections.Generic; +/// +/// Represents an IP address entry in phpIPAM. +/// [Serializable] -public class Address { - public int Id { get; } - public int SubnetId { get; } - public string Ip { get; } - public bool IsGateway { get; } - public string Description { get; } - public string Hostname { get; } - public string MAC { get; } - public string Owner { get; } - public int TagId { get; } - public int DeviceId { get; } - public string Location { get; } - public string Port { get; } - public string Note { get; } - public DateTime? LastSeen { get; } - public bool ExcludePing { get; } - public bool PTRignore { get; } - public int PTR { get; } - public string FirewallAddressObject { get; } - public DateTime? EditDate { get; } - public int CustomerId { get; } - public Object? ExtendedData { get; } - - public Address( - int id, - int subnetId, - string ip, - bool is_gateway, - string description, - string hostname, - string mac, - string owner, - int tag, - int deviceId, - string location, - string port, - string note, - DateTime? lastSeen, - bool excludePing, - bool PTRignore, - int PTR, - string firewallAddressObject, - DateTime? editDate, - int customer_id, - Object? extendedData - ) { - this.Id = id; - this.SubnetId = subnetId; - this.Ip = ip; - this.IsGateway = is_gateway; - this.Description = description; - this.Hostname = hostname; - this.MAC = mac; - this.Owner = owner; - this.TagId = tag; - this.DeviceId = deviceId; - this.Location = location; - this.Port = port; - this.Note = note; - this.LastSeen = lastSeen; - this.ExcludePing = excludePing; - this.PTRignore = PTRignore; - this.PTR = PTR; - this.FirewallAddressObject = firewallAddressObject; - this.EditDate = editDate; - this.CustomerId = customer_id; - this.ExtendedData = extendedData; - } - - public override string ToString() { - return this.Ip; - } -} \ No newline at end of file +public sealed record Address( + int Id, + int SubnetId, + string Ip, + bool IsGateway, + string Description, + string Hostname, + string MAC, + string Owner, + int TagId, + int DeviceId, + string Location, + string Port, + string Note, + DateTime? LastSeen, + bool ExcludePing, + bool PTRIgnore, + int PTR, + string FirewallAddressObject, + DateTime? EditDate, + int CustomerId, + Dictionary? ExtendedData = null +) +{ + public override string ToString() => Ip; +} diff --git a/classlib/class/domain.cs b/classlib/class/domain.cs index dc8a184..ca6322c 100644 --- a/classlib/class/domain.cs +++ b/classlib/class/domain.cs @@ -1,27 +1,17 @@ namespace PS.IPAM; + using System; +/// +/// Represents an L2 domain in phpIPAM. +/// [Serializable] -public class Domain { - public int Id { get; } - public string Name { get; } - public string Description { get; } - public string Sections { get; } - - public Domain ( - int id, - string name, - string description, - string sections - ) { - this.Id = id; - this.Name = name; - this.Description = description; - this.Sections = sections; - } - - public override string ToString() - { - return this.Name; - } -} \ No newline at end of file +public sealed record Domain( + int Id, + string Name, + string Description, + string Sections +) +{ + public override string ToString() => Name; +} diff --git a/classlib/class/nameserver.cs b/classlib/class/nameserver.cs index d692032..c11baaf 100644 --- a/classlib/class/nameserver.cs +++ b/classlib/class/nameserver.cs @@ -1,8 +1,13 @@ namespace PS.IPAM; + using System; +/// +/// Represents a nameserver configuration in phpIPAM. +/// [Serializable] -public class Nameserver { +public sealed record Nameserver +{ public int Id { get; } public string Name { get; } public string[] NameServers { get; } @@ -16,13 +21,15 @@ public class Nameserver { string nameServers, string description, string permissions, - DateTime? editDate - ) { - this.Id = id; - this.Name = name; - this.NameServers = nameServers.Split(new char[] {';'}); - this.Description = description; - this.Permissions = permissions; - this.EditDate = editDate; + DateTime? editDate) + { + Id = id; + Name = name; + NameServers = nameServers?.Split(';', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); + Description = description; + Permissions = permissions; + EditDate = editDate; } -} \ No newline at end of file + + public override string ToString() => Name; +} diff --git a/classlib/class/section.cs b/classlib/class/section.cs index 28022d1..eff437e 100644 --- a/classlib/class/section.cs +++ b/classlib/class/section.cs @@ -1,57 +1,27 @@ namespace PS.IPAM; + using System; +/// +/// Represents a section in phpIPAM that organizes subnets. +/// [Serializable] -public class Section { - public int Id { get; } - public string Name { get; } - public string Description { get; } - public int MasterSectionId { get; } - public string Permissions { get; } - public bool StrictMode { get; } - public string SubnetOrdering { get; } - public int Order { get; } - public DateTime? EditDate { get; } - public bool ShowSubnet { get; } - public bool ShowVlan { get; } - public bool ShowVRF { get; } - public bool ShowSupernetOnly { get; } - public int DNSId { get; } - - public Section ( - int id, - string name, - string description, - int masterSectionId, - string permissions, - bool strictMode, - string subnetOrdering, - int order, - DateTime? editDate, - bool showSubnet, - bool showVlan, - bool showVRF, - bool showSupernetOnly, - int dnsId - ) { - this.Id = id; - this.Name = name; - this.Description = description; - this.MasterSectionId = masterSectionId; - this.Permissions = permissions; - this.StrictMode = strictMode; - this.SubnetOrdering = subnetOrdering; - this.Order = order; - this.EditDate = editDate; - this.ShowSubnet = showSubnet; - this.ShowVlan = showVlan; - this.ShowVRF = showVRF; - this.ShowSupernetOnly = showSupernetOnly; - this.DNSId = dnsId; - } - - public override string ToString() - { - return this.Name; - } -} \ No newline at end of file +public sealed record Section( + int Id, + string Name, + string Description, + int MasterSectionId, + string Permissions, + bool StrictMode, + string SubnetOrdering, + int Order, + DateTime? EditDate, + bool ShowSubnet, + bool ShowVlan, + bool ShowVRF, + bool ShowSupernetOnly, + int DNSId +) +{ + public override string ToString() => Name; +} diff --git a/classlib/class/session.cs b/classlib/class/session.cs index a56e056..576a37b 100644 --- a/classlib/class/session.cs +++ b/classlib/class/session.cs @@ -1,33 +1,27 @@ namespace PS.IPAM; + using System; -using System.Management.Automation; +/// +/// Represents an authenticated session with phpIPAM API. +/// [Serializable] -public class Session { - public AuthType AuthType { get; } - public string Token { get; set; } - public string AppID { get; } - public string URL { get; } - public DateTime? Expires { get; set; } - public object? Credentials { get; } - public Session( - AuthType authType, - string token, - string appId, - string url, - DateTime? expires, - object? credentials - ) { - AuthType = authType; - Token = token; - AppID = appId; - URL = url; - Expires = expires; - Credentials = credentials; - } +public sealed record Session( + AuthType AuthType, + string Token, + string AppID, + string URL, + DateTime? Expires, + object? Credentials +) +{ + /// + /// Gets or sets the current authentication token. + /// + public string Token { get; set; } = Token; - public override string ToString() - { - return base.ToString(); - } -} \ No newline at end of file + /// + /// Gets or sets the token expiration time. + /// + public DateTime? Expires { get; set; } = Expires; +} diff --git a/classlib/class/subnet.cs b/classlib/class/subnet.cs index 9785ff3..8afbef5 100644 --- a/classlib/class/subnet.cs +++ b/classlib/class/subnet.cs @@ -1,114 +1,51 @@ namespace PS.IPAM; + using System; +using System.Collections.Generic; +/// +/// Represents a subnet/network in phpIPAM. +/// [Serializable] -public class Subnetwork { - public int Id { get; } - public string Subnet { get; } - public int Mask { get; } - public int SectionId { get; } - public string Description { get; } - public string LinkedSubnet { get; } - public string FirewallAddressObject { get; } - public int VrfId { get; } - public int MasterSubnetId { get; } - public bool AllowRequests { get; } - public int VlanId { get; } - public bool ShowName { get; } - public int DeviceId { get; } - public string Permissions { get; } - public bool PingSubnet { get; } - public bool DiscoverSubnet { get; } - public bool ResolveDNS { get; } - public bool DNSRecursive { get; } - public bool DNSRecords { get; } - public int NameserverId { get; } - public bool ScanAgent { get; } - public bool IsFolder { get; } - public bool IsFull { get; } - public bool IsPool { get; } - public int TagId { get; } - public int Threshold { get; } - public int LocationId { get; } - public DateTime? EditDate { get; } - public DateTime? LastScan { get; } - public DateTime? LastDiscovery { get; } - public Object Calculation { get; } - public Object? ExtendedData { get; } - public Subnetwork( - int id, - string subnet, - int mask, - int sectionId, - string description, - string linkedSubnet, - string firewallAddressObject, - int vrfId, - int masterSubnetId, - bool allowRequests, - int vlanId, - bool showName, - int deviceId, - string permissions, - bool pingSubnet, - bool discoverSubnet, - bool resolveDNS, - bool dnsRecursive, - bool dnsRecords, - int nameserverId, - bool scanAgent, - bool isFolder, - bool isFull, - bool isPool, - int tagId, - int threshold, - int locationId, - DateTime? editDate, - DateTime? lastScan, - DateTime? lastDiscovery, - Object calculation, - Object? custom_fields - ) { - this.Id = id; - this.Subnet = subnet; - this.Mask = mask; - this.SectionId = sectionId; - this.Description = description; - this.LinkedSubnet = linkedSubnet; - this.FirewallAddressObject = firewallAddressObject; - this.VrfId = vrfId; - this.MasterSubnetId = masterSubnetId; - this.AllowRequests = allowRequests; - this.VlanId = vlanId; - this.ShowName = showName; - this.DeviceId = deviceId; - this.Permissions = permissions; - this.PingSubnet = pingSubnet; - this.DiscoverSubnet = discoverSubnet; - this.ResolveDNS = resolveDNS; - this.DNSRecursive = dnsRecursive; - this.DNSRecords = dnsRecords; - this.NameserverId = nameserverId; - this.ScanAgent = scanAgent; - this.IsFolder = isFolder; - this.IsFull = isFull; - this.IsPool = isPool; - this.TagId = tagId; - this.Threshold = threshold; - this.LocationId = locationId; - this.EditDate = editDate; - this.LastScan = lastScan; - this.LastDiscovery = lastDiscovery; - this.Calculation = calculation; - this.ExtendedData = custom_fields; - } +public sealed record Subnetwork( + int Id, + string Subnet, + int Mask, + int SectionId, + string Description, + string LinkedSubnet, + string FirewallAddressObject, + int VrfId, + int MasterSubnetId, + bool AllowRequests, + int VlanId, + bool ShowName, + int DeviceId, + string Permissions, + bool PingSubnet, + bool DiscoverSubnet, + bool ResolveDNS, + bool DNSRecursive, + bool DNSRecords, + int NameserverId, + bool ScanAgent, + bool IsFolder, + bool IsFull, + bool IsPool, + int TagId, + int Threshold, + int LocationId, + DateTime? EditDate, + DateTime? LastScan, + DateTime? LastDiscovery, + object Calculation, + Dictionary? ExtendedData = null +) +{ + /// + /// Gets the subnet in CIDR notation (e.g., "192.168.1.0/24"). + /// + public string CIDR => $"{Subnet}/{Mask}"; - public string GetCIDR() { - return $"{this.Subnet}/{this.Mask}"; - } - - public override string ToString() - { - return this.GetCIDR(); - } -} \ No newline at end of file + public override string ToString() => CIDR; +} diff --git a/classlib/class/tag.cs b/classlib/class/tag.cs index ddc94e5..6231e37 100644 --- a/classlib/class/tag.cs +++ b/classlib/class/tag.cs @@ -1,45 +1,21 @@ namespace PS.IPAM; + using System; +/// +/// Represents an address tag in phpIPAM. +/// [Serializable] -public class Tag { - public int Id { get; } - public string Type { get; } - public bool ShowTag { get; } - public string BGColor { get; } - public string FGColor { get; } - public bool Compress { get; } - public bool Locked { get; } - public bool UpdateTag { get; } - - public Tag( - int id, - string type, - bool showTag, - string BGColor, - string FGColor, - string compress, - string locked, - bool updateTag - ) { - this.Id = id; - this.Type = type; - this.ShowTag = showTag; - this.BGColor = BGColor; - this.FGColor = FGColor; - this.Compress = this.StringToBool(compress); - this.Locked = this.StringToBool(locked); - this.UpdateTag = updateTag; - } - - public override string ToString() { - return this.Type; - } - - private bool StringToBool(string str) { - if (str == "Yes") { - return true; - } - return false; - } -} \ No newline at end of file +public sealed record Tag( + int Id, + string Type, + bool ShowTag, + string BackgroundColor, + string ForegroundColor, + string Compress, + string Locked, + bool UpdateTag +) +{ + public override string ToString() => Type; +} diff --git a/classlib/class/vlan.cs b/classlib/class/vlan.cs index ec080b9..b9ea921 100644 --- a/classlib/class/vlan.cs +++ b/classlib/class/vlan.cs @@ -1,41 +1,27 @@ namespace PS.IPAM; + using System; -using System.Dynamic; +using System.Collections.Generic; +/// +/// Represents a VLAN in phpIPAM. +/// [Serializable] -public class Vlan : DynamicObject { - public int Id { get; } - public int VlanId { get; } - public int DomainId { get; } - public string Name { get; } - public int Number { get; } - public string Description { get; } - public DateTime? EditDate { get; } - public int CustomerId { get; } - public Object? ExtendedData { get; } - public Vlan ( - int vlanId, - int domainId, - string name, - int number, - string description, - DateTime? editDate, - int customer_id, - Object? custom_fields - ) { - this.Id = vlanId; - this.VlanId = vlanId; - this.DomainId = domainId; - this.Name = name; - this.Number = number; - this.Description = description; - this.EditDate = editDate; - this.CustomerId = customer_id; - this.ExtendedData = custom_fields; - } +public sealed record Vlan( + int Id, + int DomainId, + string Name, + int Number, + string Description, + DateTime? EditDate, + int CustomerId, + Dictionary? ExtendedData = null +) +{ + /// + /// Alias for Id to maintain API compatibility. + /// + public int VlanId => Id; - public override string ToString() - { - return $"{this.Number}"; - } -} \ No newline at end of file + public override string ToString() => Number.ToString(); +} diff --git a/classlib/class/vrf.cs b/classlib/class/vrf.cs index 2c426ac..e925e17 100644 --- a/classlib/class/vrf.cs +++ b/classlib/class/vrf.cs @@ -1,35 +1,21 @@ namespace PS.IPAM; + using System; +using System.Collections.Generic; +/// +/// Represents a VRF (Virtual Routing and Forwarding) instance in phpIPAM. +/// [Serializable] -public class Vrf { - public int Id { get; } - public string Name { get; } - public string RouteDistinguisher { get; } - public string Description { get; } - public string Sections { get; } - public DateTime? EditDate { get; } - public Object? ExtendedData { get; } - - public Vrf( - int id, - string name, - string rd, - string description, - string sections, - DateTime? editDate, - Object? custom_fields - ) { - this.Id = id; - this.Name = name; - this.RouteDistinguisher = rd; - this.Description = description; - this.Sections = sections; - this.EditDate = editDate; - this.ExtendedData = custom_fields; - } - public override string ToString() - { - return this.Name; - } -} \ No newline at end of file +public sealed record Vrf( + int Id, + string Name, + string RouteDistinguisher, + string Description, + string Sections, + DateTime? EditDate, + Dictionary? ExtendedData = null +) +{ + public override string ToString() => Name; +} diff --git a/classlib/enum/SessionStatus.cs b/classlib/enum/SessionStatus.cs new file mode 100644 index 0000000..0c9c73c --- /dev/null +++ b/classlib/enum/SessionStatus.cs @@ -0,0 +1,22 @@ +namespace PS.IPAM; + +/// +/// Represents the current status of an API session. +/// +public enum SessionStatus +{ + /// + /// No session exists or no token is available. + /// + NoSession, + + /// + /// The session token has expired. + /// + Expired, + + /// + /// The session is valid and ready for use. + /// + Valid +} diff --git a/classlib/enum/authType.cs b/classlib/enum/authType.cs index bb513c6..6f5f499 100644 --- a/classlib/enum/authType.cs +++ b/classlib/enum/authType.cs @@ -1,8 +1,20 @@ namespace PS.IPAM; + using System; +/// +/// Specifies the authentication method for phpIPAM API. +/// [Serializable] -public enum AuthType { - credentials, - token -} \ No newline at end of file +public enum AuthType +{ + /// + /// Authentication using username and password credentials. + /// + Credentials, + + /// + /// Authentication using a static API token. + /// + Token +} diff --git a/classlib/enum/controllers.cs b/classlib/enum/controllers.cs index 7d6c6a6..1a0f597 100644 --- a/classlib/enum/controllers.cs +++ b/classlib/enum/controllers.cs @@ -1,14 +1,19 @@ namespace PS.IPAM; + using System; +/// +/// Represents the main API controllers in phpIPAM. +/// [Serializable] -public enum controllers { - user, - vlan, - subnets, - addresses, - sections, - vrf, - l2domains, - tools -} \ No newline at end of file +public enum ApiController +{ + User, + Vlan, + Subnets, + Addresses, + Sections, + Vrf, + L2Domains, + Tools +} diff --git a/classlib/enum/subcontrollers.cs b/classlib/enum/subcontrollers.cs index a674d67..bc148d4 100644 --- a/classlib/enum/subcontrollers.cs +++ b/classlib/enum/subcontrollers.cs @@ -1,16 +1,21 @@ namespace PS.IPAM; + using System; +/// +/// Represents sub-controllers/endpoints within main API controllers. +/// [Serializable] -public enum subcontrollers { - nameservers, - tags, - devices, - device_types, - vlans, - vrfs, - scanagents, - locations, - nat, - racks -} \ No newline at end of file +public enum ApiSubController +{ + Nameservers, + Tags, + Devices, + DeviceTypes, + Vlans, + Vrfs, + ScanAgents, + Locations, + Nat, + Racks +} diff --git a/classlib/enum/types.cs b/classlib/enum/types.cs index 6bc4a56..2424633 100644 --- a/classlib/enum/types.cs +++ b/classlib/enum/types.cs @@ -1,8 +1,13 @@ namespace PS.IPAM; + using System; +/// +/// Represents the model types returned by the phpIPAM API. +/// [Serializable] -public enum types { +public enum ModelType +{ Address, Domain, Section, @@ -11,4 +16,4 @@ public enum types { Tag, Vlan, Vrf -} \ No newline at end of file +}