diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48735341d6a5..39259134ee97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -349,3 +349,16 @@ jobs: verbose: true name: codecov token: ${{ secrets.CODECOV_TOKEN }} + + allowed-values: + if: github.repository == 'apache/cloudstack' + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Check allowedValues annotations + run: python3 scripts/check_allowed_values.py diff --git a/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java b/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java index 13f351f3a27f..657917b9ad5b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java @@ -32,8 +32,12 @@ public abstract class AbstractGetUploadParamsCmd extends BaseCmd { @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the Volume/Template/ISO") private String name; - @Parameter(name = ApiConstants.FORMAT, type = CommandType.STRING, required = true, description = "The format for the Volume/Template/ISO. Possible values include QCOW2, OVA, " - + "and VHD.") + @Parameter(name = ApiConstants.FORMAT, + type = CommandType.STRING, + allowedValues = {"QCOW2", "OVA", "VHD"}, + required = true, + description = "The format for the Volume/Template/ISO. Possible values include QCOW2, OVA, " + + "and VHD.") private String format; @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "The ID of the zone the Volume/Template/ISO is " diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java index 94c5d8ff39fc..de302594bfd6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java @@ -83,7 +83,9 @@ public abstract class BaseUpdateTemplateOrIsoCmd extends BaseCmd { description = "Optional boolean field, which indicates if details should be cleaned up or not (if set to true, details removed for this resource, details field ignored; if false or not set, no action)") private Boolean cleanupDetails; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "the CPU arch of the template/ISO. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; diff --git a/api/src/main/java/org/apache/cloudstack/api/Parameter.java b/api/src/main/java/org/apache/cloudstack/api/Parameter.java index e354fda02d5a..c42578bebdee 100644 --- a/api/src/main/java/org/apache/cloudstack/api/Parameter.java +++ b/api/src/main/java/org/apache/cloudstack/api/Parameter.java @@ -52,5 +52,9 @@ ApiArgValidator[] validations() default {}; + String[] allowedValues() default {}; + + Class allowedValueType() default Enum.class; + boolean acceptedOnAdminPort() default true; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java index d82cc852e4ff..4a864ed34388 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java @@ -49,10 +49,16 @@ public class ListRolesCmd extends BaseListCmd { @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List role by role name.") private String roleName; - @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "List role by role type, valid options are: Admin, ResourceAdmin, DomainAdmin, User.") + @Parameter(name = ApiConstants.TYPE, + type = CommandType.STRING, + allowedValues = {"Admin", "ResourceAdmin", "DomainAdmin", "User"}, + description = "List role by role type, valid options are: Admin, ResourceAdmin, DomainAdmin, User.") private String roleType; - @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "List role by role type status, valid options are: enabled, disabled") + @Parameter(name = ApiConstants.STATE, + type = CommandType.STRING, + allowedValues = {"enabled", "disabled"}, + description = "List role by role type status, valid options are: enabled, disabled") private String roleState; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java index b3d816adc3fb..e6bd5223653d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java @@ -31,7 +31,10 @@ public abstract class RoleCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User") + @Parameter(name = ApiConstants.TYPE, + type = CommandType.STRING, + allowedValues = {"Admin", "ResourceAdmin", "DomainAdmin", "User"}, + description = "The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User") private String roleType; @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the role") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java index fcaba5154e1f..05772fe60b48 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java @@ -51,7 +51,9 @@ public class ListAnnotationsCmd extends BaseListCmd { private String userUuid; @Parameter(name = ApiConstants.ANNOTATION_FILTER, - type = CommandType.STRING, since = "4.16.0", + type = CommandType.STRING, + allowedValues = {"self", "all"}, + since = "4.16.0", description = "Possible values are \"self\" and \"all\". " + "* self : annotations that have been created by the calling user. " + "* all : all the annotations the calling user can access") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java index d8fa2123d228..4c67cdaf89a0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java @@ -68,7 +68,9 @@ public class AddClusterCmd extends BaseCmd { description = "Hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3,External") private String hypervisor; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "The CPU arch of the cluster. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; @@ -89,15 +91,17 @@ public class AddClusterCmd extends BaseCmd { private String vsmipaddress; @Parameter(name = ApiConstants.VSWITCH_TYPE_GUEST_TRAFFIC, - type = CommandType.STRING, - required = false, - description = "Type of virtual switch used for guest traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)") + type = CommandType.STRING, + allowedValues = {"vmwaresvs", "vmwaredvs"}, + required = false, + description = "Type of virtual switch used for guest traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)") private String vSwitchTypeGuestTraffic; @Parameter(name = ApiConstants.VSWITCH_TYPE_PUBLIC_TRAFFIC, - type = CommandType.STRING, - required = false, - description = "Type of virtual switch used for public traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)") + type = CommandType.STRING, + allowedValues = {"vmwaresvs", "vmwaredvs"}, + required = false, + description = "Type of virtual switch used for public traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)") private String vSwitchTypePublicTraffic; @Parameter(name = ApiConstants.VSWITCH_NAME_GUEST_TRAFFIC, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java index 77d0557af05b..dd9c69b299fe 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java @@ -57,7 +57,9 @@ public class UpdateClusterCmd extends BaseCmd { @Parameter(name = ApiConstants.MANAGED_STATE, type = CommandType.STRING, description = "Whether this cluster is managed by cloudstack") private String managedState; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "the CPU arch of the cluster. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java index d1f22baf6604..442b064f1575 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java @@ -69,7 +69,10 @@ public class RunDiagnosticsCmd extends BaseAsyncCmd { description = "The IP/Domain address to test connection to") private String address; - @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, + @Parameter(name = ApiConstants.TYPE, + type = CommandType.STRING, + required = true, + allowedValues = {"ping", "traceroute", "arping"}, description = "The System VM diagnostics type valid options are: ping, traceroute, arping") private String type; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java index c085abd42c76..648bcbfa1624 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java @@ -53,8 +53,9 @@ public class UpdateHostCmd extends BaseCmd { private Long osCategoryId; @Parameter(name = ApiConstants.ALLOCATION_STATE, - type = CommandType.STRING, - description = "Change resource state of host, valid values are [Enable, Disable]. Operation may failed if host in states not allowing Enable/Disable") + type = CommandType.STRING, + allowedValues = {"Enable", "Disable"}, + description = "Change resource state of host, valid values are [Enable, Disable]. Operation may failed if host in states not allowing Enable/Disable") private String allocationState; @Parameter(name = ApiConstants.HOST_TAGS, type = CommandType.LIST, collectionType = CommandType.STRING, description = "List of tags to be added to the host") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java index 1c832b7217ef..9fa9385bb9af 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java @@ -155,8 +155,10 @@ public abstract class NetworkOfferingBaseCmd extends BaseCmd { @Parameter(name = ApiConstants.NETWORK_MODE, type = CommandType.STRING, + allowedValues = {"NATTED", "ROUTED"}, description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED", - since = "4.20.0") + since = "4.20.0", + allowedValueType = NetworkOffering.NetworkMode.class) private String networkMode; @Parameter(name = ApiConstants.FOR_TUNGSTEN, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java index e1ff90e4bd89..0c82419a3984 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java @@ -40,6 +40,7 @@ import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; +import com.cloud.storage.Storage; import com.cloud.storage.Storage.ProvisioningType; import com.cloud.user.Account; @@ -85,9 +86,11 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.STORAGE_TYPE, type = CommandType.STRING, description = "The storage type of the disk offering. Values are local and shared.") private String storageType = ServiceOffering.StorageType.shared.toString(); - @Parameter(name = ApiConstants.PROVISIONINGTYPE, + @Parameter( + name = ApiConstants.PROVISIONINGTYPE, type = CommandType.STRING, - description = "Provisioning type used to create volumes. Valid values are thin, sparse, fat.") + description = "Provisioning type used to create volumes. Valid values are thin, sparse, fat.", + allowedValueType = Storage.ProvisioningType.class) private String provisioningType = ProvisioningType.THIN.toString(); @Parameter(name = ApiConstants.DISPLAY_OFFERING, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index 4363d6861ba1..a41ed1b1802c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -67,7 +67,11 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the service offering, defaults to 'name'.") private String displayText; - @Parameter(name = ApiConstants.PROVISIONINGTYPE, type = CommandType.STRING, description = "Provisioning type used to create volumes. Valid values are thin, sparse, fat.") + @Parameter( + name = ApiConstants.PROVISIONINGTYPE, + type = CommandType.STRING, + description = "Provisioning type used to create volumes. Valid values are thin, sparse, fat.", + allowedValueType = Storage.ProvisioningType.class) private String provisioningType = Storage.ProvisioningType.THIN.toString(); @Parameter(name = ApiConstants.MEMORY, type = CommandType.INTEGER, required = false, description = "The total memory of the service offering in MB") @@ -259,7 +263,10 @@ public class CreateServiceOfferingCmd extends BaseCmd { since = "4.21.0") private Integer leaseDuration; - @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0", + @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, + type = CommandType.STRING, + since = "4.21.0", + allowedValues = {"STOP", "DESTROY"}, description = "Lease expiry action, valid values are STOP and DESTROY") private String leaseExpiryAction; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java index 0fe83fd0796c..e0e1070effbe 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java @@ -66,8 +66,17 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd implements UserCmd description = "Flag to display the resource icon for users") private Boolean showIcon; - @Parameter(name = ApiConstants.USER_SOURCE, type = CommandType.STRING, since = "4.21.0.0", - description = "List users by their authentication source. Valid values are: native, ldap, saml2 and saml2disabled.") + @Parameter( + name = ApiConstants.USER_SOURCE, + type = CommandType.STRING, + since = "4.21.0.0", + description = "List users by their authentication source. Valid values are: native, ldap, saml2 and saml2disabled.", + allowedValues = { + "native", + "ldap", + "saml2", + "saml2disabled" + }) private String userSource; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java index 94b547ff4267..7886b7f577c6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java @@ -75,7 +75,10 @@ public class ListImportVMTasksCmd extends BaseListCmd { description = "Conversion host of the importing task") private Long convertHostId; - @Parameter(name = ApiConstants.TASKS_FILTER, type = CommandType.STRING, description = "Filter tasks by state, valid options are: All, Running, Completed, Failed") + @Parameter(name = ApiConstants.TASKS_FILTER, + type = CommandType.STRING, + allowedValues = {"All", "Running", "Completed", "Failed"}, + description = "Filter tasks by state, valid options are: All, Running, Completed, Failed") private String tasksFilter; public Long getZoneId() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index 2b934a60da7a..645180db43e6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -92,10 +92,15 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "Desired service capabilities as part of VPC offering", since = "4.4") private Map> serviceCapabilityList; - @Parameter(name = ApiConstants.INTERNET_PROTOCOL, - type = CommandType.STRING, - description = "The internet protocol of the offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create an offering that supports both IPv4 and IPv6", - since = "4.17.0") + @Parameter( + name = ApiConstants.INTERNET_PROTOCOL, + type = CommandType.STRING, + description = "The internet protocol of the offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create an offering that supports both IPv4 and IPv6", + since = "4.17.0", + allowedValues = { + "IPv4", + "DualStack" + }) private String internetProtocol; @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, @@ -144,10 +149,12 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { since = "4.16") private Boolean enable; - @Parameter(name = ApiConstants.NETWORK_MODE, - type = CommandType.STRING, - description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED", - since = "4.20.0") + @Parameter( + name = ApiConstants.NETWORK_MODE, + type = CommandType.STRING, + description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED", + since = "4.20.0", + allowedValueType = NetworkOffering.NetworkMode.class) private String networkMode; @Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0", @@ -157,7 +164,8 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.ROUTING_MODE, type = CommandType.STRING, since = "4.20.0", - description = "the routing mode for the VPC offering. Supported types are: Static or Dynamic.") + description = "the routing mode for the VPC offering. Supported types are: Static or Dynamic.", + allowedValueType = NetworkOffering.RoutingMode.class) private String routingMode; @Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java index 61745ccda7d1..22f44c253d5b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java @@ -47,7 +47,12 @@ public class CreateConditionCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.COUNTER_ID, type = CommandType.UUID, entityType = CounterResponse.class, required = true, description = "ID of the Counter.") private long counterId; - @Parameter(name = ApiConstants.RELATIONAL_OPERATOR, type = CommandType.STRING, required = true, description = "Relational Operator to be used with threshold. Valid values are EQ, GT, LT, GE, LE.") + @Parameter( + name = ApiConstants.RELATIONAL_OPERATOR, + type = CommandType.STRING, + required = true, + description = "Relational Operator to be used with threshold. Valid values are EQ, GT, LT, GE, LE.", + allowedValueType = Condition.Operator.class) private String relationalOperator; @Parameter(name = ApiConstants.THRESHOLD, type = CommandType.LONG, required = true, description = "Value for which the Counter will be evaluated with the Operator selected.") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java index 43de212da7b9..eb374b7b27a9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java @@ -49,7 +49,12 @@ public class UpdateConditionCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ConditionResponse.class, required = true, description = "The ID of the condition.") private Long id; - @Parameter(name = ApiConstants.RELATIONAL_OPERATOR, type = CommandType.STRING, required = true, description = "Relational Operator to be used with threshold. Valid values are EQ, GT, LT, GE, LE.") + @Parameter( + name = ApiConstants.RELATIONAL_OPERATOR, + type = CommandType.STRING, + required = true, + description = "Relational Operator to be used with threshold. Valid values are EQ, GT, LT, GE, LE.", + allowedValueType = Condition.Operator.class) private String relationalOperator; @Parameter(name = ApiConstants.THRESHOLD, type = CommandType.LONG, required = true, description = "Value for which the Counter will be evaluated with the Operator selected.") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index 67ad7c71503f..8b02dc13b1b6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -56,9 +56,10 @@ public class CreateBackupScheduleCmd extends BaseCmd { private Long vmId; @Parameter(name = ApiConstants.INTERVAL_TYPE, - type = CommandType.STRING, - required = true, - description = "Valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") + type = CommandType.STRING, + required = true, + description = "Valid values are HOURLY, DAILY, WEEKLY, and MONTHLY", + allowedValueType = DateUtil.IntervalType.class) private String intervalType; @Parameter(name = ApiConstants.SCHEDULE, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java index 3fd571b7a479..888048fa01bf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java @@ -61,9 +61,10 @@ public class CreateEgressFirewallRuleCmd extends BaseAsyncCreateCmd implements F private Long networkId; @Parameter(name = ApiConstants.PROTOCOL, - type = CommandType.STRING, - required = true, - description = "The protocol for the firewall rule. Valid values are TCP/UDP/ICMP.") + type = CommandType.STRING, + allowedValues = {"TCP", "UDP", "ICMP"}, + required = true, + description = "The protocol for the firewall rule. Valid values are TCP/UDP/ICMP.") private String protocol; @Parameter(name = ApiConstants.START_PORT, type = CommandType.INTEGER, description = "The starting port of firewall rule") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java index bc65126f33bd..7944365ebb5e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java @@ -61,9 +61,10 @@ public class CreateFirewallRuleCmd extends BaseAsyncCreateCmd implements Firewal private Long ipAddressId; @Parameter(name = ApiConstants.PROTOCOL, - type = CommandType.STRING, - required = true, - description = "The protocol for the firewall rule. Valid values are TCP/UDP/ICMP.") + type = CommandType.STRING, + required = true, + description = "The protocol for the firewall rule. Valid values are TCP/UDP/ICMP.", + allowedValues = {"TCP", "UDP", "ICMP"}) private String protocol; @Parameter(name = ApiConstants.START_PORT, type = CommandType.INTEGER, description = "The starting port of firewall rule") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java index 2bc5fc2ee68b..874c34533cb1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java @@ -73,9 +73,10 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P private Integer privateStartPort; @Parameter(name = ApiConstants.PROTOCOL, - type = CommandType.STRING, - required = true, - description = "The protocol for the port forwarding rule. Valid values are TCP or UDP.") + type = CommandType.STRING, + allowedValues = {"TCP", "UDP"}, + required = true, + description = "The protocol for the port forwarding rule. Valid values are TCP or UDP.") private String protocol; @Parameter(name = ApiConstants.PRIVATE_END_PORT, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java index 237af7e4601b..70c66e34dea0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java @@ -79,7 +79,11 @@ public class CreateIpv6FirewallRuleCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class, description = "The Network of the Instance the Ipv6 firewall rule will be created for", required = true) private Long networkId; - @Parameter(name = ApiConstants.TRAFFIC_TYPE, type = CommandType.STRING, description = "The traffic type for the Ipv6 firewall rule, can be ingress or egress, defaulted to ingress if not specified") + @Parameter( + name = ApiConstants.TRAFFIC_TYPE, + type = CommandType.STRING, + description = "The traffic type for the Ipv6 firewall rule, can be ingress or egress, defaulted to ingress if not specified", + allowedValueType = FirewallRule.TrafficType.class) private String trafficType; @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "An optional field, whether to the display the rule to the end User or not", authorized = {RoleType.Admin}) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java index 562cbc2c623d..86ea66653ab0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java @@ -61,14 +61,16 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { private Boolean ready; @Parameter(name = ApiConstants.ISO_FILTER, - type = CommandType.STRING, - description = "Possible values are \"featured\", \"self\", \"selfexecutable\",\"sharedexecutable\",\"executable\", and \"community\". " - + "* featured : Templates that have been marked as featured and public. " - + "* self : Templates that have been registered or created by the calling User. " - + "* selfexecutable : same as self, but only returns Templates that can be used to deploy a new Instance. " - + "* sharedexecutable : Templates ready to be deployed that have been granted to the calling User by another User. " - + "* executable : Templates that are owned by the calling User, or public Templates, that can be used to deploy an Instance. " - + "* community : Templates that have been marked as public but not featured. " + "* all : all Templates (only usable by admins).") + type = CommandType.STRING, + description = "Possible values are \"featured\", \"self\", \"selfexecutable\",\"sharedexecutable\",\"executable\", and \"community\". " + + "* featured : Templates that have been marked as featured and public. " + + "* self : Templates that have been registered or created by the calling User. " + + "* selfexecutable : same as self, but only returns Templates that can be used to deploy a new Instance. " + + "* sharedexecutable : Templates ready to be deployed that have been granted to the calling User by another User. " + + "* executable : Templates that are owned by the calling User, or public Templates, that can be used to deploy an Instance. " + + "* community : Templates that have been marked as public but not featured. " + + "* all : all Templates (only usable by admins).", + allowedValueType = TemplateFilter.class) private String isoFilter = TemplateFilter.selfexecutable.toString(); @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List all ISOs by name") @@ -86,9 +88,11 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "Flag to display the resource image for the ISOs") private Boolean showIcon; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64, s390x", - since = "4.20") + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64, s390x", + since = "4.20", + allowedValueType = CPU.CPUArch.class) private String arch; @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType= GuestOSCategoryResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java index 1c57e902e221..cbf54add03fd 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java @@ -119,7 +119,9 @@ public class RegisterIsoCmd extends BaseCmd implements UserCmd { description = "True if password reset feature is supported; default is false") private Boolean passwordEnabled; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java index e816e0f95ebb..51a85a1d414d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java @@ -66,6 +66,7 @@ public class CreateLBStickinessPolicyCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.METHOD_NAME, type = CommandType.STRING, required = true, + allowedValues = {"LbCookie", "AppCookie", "SourceBased"}, description = "Name of the load balancer stickiness policy method, possible values are LbCookie, AppCookie, SourceBased") private String stickinessMethodName; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java index 7963dfe5c7d3..bbbcd6887908 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java @@ -63,7 +63,15 @@ public class CreateIpForwardingRuleCmd extends BaseAsyncCreateCmd implements Sta @Parameter(name = ApiConstants.END_PORT, type = CommandType.INTEGER, description = "The end port for the rule") private Integer endPort; - @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, required = true, description = "The protocol for the rule. Valid values are TCP or UDP.") + @Parameter( + name = ApiConstants.PROTOCOL, + type = CommandType.STRING, + required = true, + description = "The protocol for the rule. Valid values are TCP or UDP.", + allowedValues = { + "TCP", + "UDP" + }) private String protocol; @Parameter(name = ApiConstants.OPEN_FIREWALL, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java index 79f2cf8c7449..08d78cb2f36a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java @@ -111,6 +111,7 @@ public class ListNetworksCmd extends BaseListRetrieveOnlyResourceCountCmd implem @Parameter(name = ApiConstants.NETWORK_FILTER, type = CommandType.STRING, + allowedValues = {"account", "domain", "accountdomain", "shared", "all"}, since = "4.17.0", description = "Possible values are \"account\", \"domain\", \"accountdomain\",\"shared\", and \"all\". Default value is \"all\"." + "* account : account networks that have been registered for or created by the calling User. " diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java index b1e7b2a00040..8fb606d8abfb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java @@ -23,6 +23,7 @@ import com.cloud.storage.snapshot.SnapshotPolicy; import com.cloud.user.Account; import java.util.ArrayList; +import com.cloud.utils.DateUtil; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -52,7 +53,11 @@ public class CreateSnapshotPolicyCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.INTERVAL_TYPE, type = CommandType.STRING, required = true, description = "Valid values are HOURLY, DAILY, WEEKLY, and MONTHLY") + @Parameter(name = ApiConstants.INTERVAL_TYPE, + type = CommandType.STRING, + required = true, + description = "Valid values are HOURLY, DAILY, WEEKLY, and MONTHLY", + allowedValueType = DateUtil.IntervalType.class) private String intervalType; @Parameter(name = ApiConstants.MAX_SNAPS, type = CommandType.INTEGER, required = true, description = "Maximum number of Snapshots to retain") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java index 316b9c8d0c9d..d31585ad69d9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java @@ -46,13 +46,19 @@ public class ListSnapshotsCmd extends BaseListTaggedResourcesCmd { @Parameter(name=ApiConstants.IDS, type=CommandType.LIST, collectionType=CommandType.UUID, entityType=SnapshotResponse.class, description = "The IDs of the Snapshots, mutually exclusive with id", since = "4.9") private List ids; - @Parameter(name = ApiConstants.INTERVAL_TYPE, type = CommandType.STRING, description = "Valid values are HOURLY, DAILY, WEEKLY, and MONTHLY.") + @Parameter(name = ApiConstants.INTERVAL_TYPE, + type = CommandType.STRING, + description = "Valid values are HOURLY, DAILY, WEEKLY, and MONTHLY.", + allowedValueType = DateUtil.IntervalType.class) private String intervalType; @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Lists Snapshot by Snapshot name") private String snapshotName; - @Parameter(name = ApiConstants.SNAPSHOT_TYPE, type = CommandType.STRING, description = "Valid values are MANUAL or RECURRING.") + @Parameter(name = ApiConstants.SNAPSHOT_TYPE, + type = CommandType.STRING, + description = "Valid values are MANUAL or RECURRING.", + allowedValueType = Snapshot.Type.class) private String snapshotType; @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.UUID, entityType = VolumeResponse.class, description = "The ID of the disk volume") @@ -64,8 +70,15 @@ public class ListSnapshotsCmd extends BaseListTaggedResourcesCmd { @Parameter(name = ApiConstants.SHOW_UNIQUE, type = CommandType.BOOLEAN, description = "If set to false, list templates across zones and their storages", since = "4.19.0") private Boolean showUnique; - @Parameter(name = ApiConstants.LOCATION_TYPE, type = CommandType.STRING, description = "list snapshots by location type. Used only when showunique=false. " + - "Valid location types: 'primary', 'secondary'. Default is empty", since = "4.19.0") + @Parameter(name = ApiConstants.LOCATION_TYPE, + type = CommandType.STRING, + description = "list snapshots by location type. Used only when showunique=false. " + + "Valid location types: 'primary', 'secondary'. Default is empty", + since = "4.19.0", + allowedValues = { + "primary", + "secondary" + }) private String locationType; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java index b5e41ff449ca..ac80f99f5ce4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java @@ -149,7 +149,9 @@ public class CreateTemplateCmd extends BaseAsyncCreateCmd implements UserCmd { since = "4.19.0") private String accountName; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x. Defaults to x86_64", since = "4.20.2") private String arch; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java index e6e178baada6..37be2c2dd8f6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java @@ -56,7 +56,9 @@ public class GetUploadParamsForTemplateCmd extends AbstractGetUploadParamsCmd { description = "The ID of the OS Type that best represents the OS of this Template. Not required for VMware as the guest OS is obtained from the OVF file.") private Long osTypeId; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; @@ -104,7 +106,9 @@ public class GetUploadParamsForTemplateCmd extends AbstractGetUploadParamsCmd { description = "if true, the templates would be available for deploying CKS clusters", since = "4.21.0") protected Boolean forCks; - @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, + @Parameter(name = ApiConstants.TEMPLATE_TYPE, + type = CommandType.STRING, + allowedValues = {"USER", "VNF", "SYSTEM", "ROUTING", "BUILTIN"}, description = "the type of the template. Valid options are: USER/VNF (for all users) and SYSTEM/ROUTING/BUILTIN (for admins only).", since = "4.22.0") private String templateType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index 0b52413aaf19..98da6db41c25 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -67,15 +67,17 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User private String templateName; @Parameter(name = ApiConstants.TEMPLATE_FILTER, - type = CommandType.STRING, - required = true, - description = "Possible values are \"featured\", \"self\", \"selfexecutable\",\"sharedexecutable\",\"executable\", and \"community\". " - + "* featured : Templates that have been marked as featured and public. " - + "* self : Templates that have been registered or created by the calling user. " - + "* selfexecutable : same as self, but only returns Templates that can be used to deploy a new Instance. " - + "* sharedexecutable : Templates ready to be deployed that have been granted to the calling user by another user. " - + "* executable : Templates that are owned by the calling user, or public Templates, that can be used to deploy an Instance. " - + "* community : Templates that have been marked as public but not featured. " + "* all : all Templates (only usable by admins).") + type = CommandType.STRING, + required = true, + description = "Possible values are \"featured\", \"self\", \"selfexecutable\", \"sharedexecutable\", \"executable\", and \"community\". " + + "* featured : Templates that have been marked as featured and public. " + + "* self : Templates that have been registered or created by the calling user. " + + "* selfexecutable : same as self, but only returns Templates that can be used to deploy a new Instance. " + + "* sharedexecutable : Templates ready to be deployed that have been granted to the calling user by another user. " + + "* executable : Templates that are owned by the calling user, or public Templates, that can be used to deploy an Instance. " + + "* community : Templates that have been marked as public but not featured. " + + "* all : all Templates (only usable by admins).", + allowedValueType = TemplateFilter.class) private String templateFilter; @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "List Templates by zoneId") @@ -111,9 +113,11 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User since = "4.21.0") private Boolean forCks; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", - since = "4.20") + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", + since = "4.20", + allowedValueType = CPU.CPUArch.class) private String arch; @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java index 49992ac66611..124977165c90 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java @@ -67,9 +67,15 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { private String displayText; @Parameter(name = ApiConstants.FORMAT, - type = CommandType.STRING, - required = true, - description = "The format for the Template. Possible values include QCOW2, RAW, VHD and OVA.") + type = CommandType.STRING, + required = true, + description = "The format for the Template. Possible values include QCOW2, RAW, VHD and OVA.", + allowedValues = { + "QCOW2", + "RAW", + "VHD", + "OVA" + }) private String format; @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, description = "The target hypervisor for the Template") @@ -174,14 +180,22 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { description = "if true, the templates would be available for deploying CKS clusters", since = "4.21.0") protected Boolean forCks; - @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, + @Parameter(name = ApiConstants.TEMPLATE_TYPE, + type = CommandType.STRING, + allowedValues = {"USER", "VNF", "SYSTEM", "ROUTING", "BUILTIN"}, description = "the type of the template. Valid options are: USER/VNF (for all users) and SYSTEM/ROUTING/BUILTIN (for admins only).", since = "4.19.0") private String templateType; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", - since = "4.20") + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", + since = "4.20", + allowedValues = { + "x86_64", + "aarch64", + "s390x" + }) private String arch; @Parameter(name = ApiConstants.EXTENSION_ID, type = CommandType.UUID, entityType = ExtensionResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java index 56d50285692d..004dd4f918a2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java @@ -39,7 +39,9 @@ public class UpdateTemplateCmd extends BaseUpdateTemplateOrIsoCmd implements Use //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, + @Parameter(name = ApiConstants.TEMPLATE_TYPE, + type = CommandType.STRING, + allowedValues = {"USER", "VNF", "SYSTEM", "ROUTING", "BUILTIN"}, description = "The type of the Template. Valid options are: USER/VNF (for all users) and SYSTEM/ROUTING/BUILTIN (for admins only).") private String templateType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmd.java index c8c6d17d4162..22d4f3ad75c6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/LinkUserDataToTemplateCmd.java @@ -64,6 +64,7 @@ public class LinkUserDataToTemplateCmd extends BaseCmd implements AdminCmd { @Parameter(name = ApiConstants.USER_DATA_POLICY, type = CommandType.STRING, + allowedValues = {"ALLOWOVERRIDE", "APPEND", "DENYOVERRIDE"}, description = "An optional override policy of the userdata. Possible values are - ALLOWOVERRIDE, APPEND, DENYOVERRIDE. Default policy is allowoverride") private String userdataPolicy; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java index 8c29d7338b85..17013d2c5e6f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java @@ -268,7 +268,10 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme description = "Number of days instance is leased for.") private Integer leaseDuration; - @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0", + @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, + type = CommandType.STRING, + since = "4.21.0", + allowedValues = {"STOP", "DESTROY"}, description = "Lease expiry action, valid values are STOP and DESTROY") private String leaseExpiryAction; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java index 46e3c7926f4e..ca274af377f6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java @@ -85,7 +85,10 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name of the Instance (a substring match is made against the parameter value, data for all matching Instances will be returned)") private String name; - @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "State of the Instance. Possible values are: Running, Stopped, Present, Destroyed, Expunged. Present is used for the state equal not destroyed.") + @Parameter(name = ApiConstants.STATE, + type = CommandType.STRING, + allowedValues = {"Running", "Stopped", "Present", "Destroyed", "Expunged"}, + description = "State of the Instance. Possible values are: Running, Stopped, Present, Destroyed, Expunged. Present is used for the state equal not destroyed.") private String state; @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "The availability zone ID") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java index e3ad0502f454..43f813a4f47a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java @@ -159,7 +159,10 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction, description = "Number of days to lease the instance from now onward. Use -1 to remove the existing lease") private Integer leaseDuration; - @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0", + @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, + type = CommandType.STRING, + since = "4.21.0", + allowedValues = {"STOP", "DESTROY"}, description = "Lease expiry action, valid values are STOP and DESTROY") private String leaseExpiryAction; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java index fdbd4a61c072..8331f59df7c6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java @@ -53,7 +53,11 @@ public class CheckAndRepairVolumeCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "The ID of the volume") private Long id; - @Parameter(name = ApiConstants.REPAIR, type = CommandType.STRING, required = false, description = "parameter to repair the volume, leaks or all are the possible values") + @Parameter(name = ApiConstants.REPAIR, + type = CommandType.STRING, + allowedValues = {"LEAKS", "ALL"}, + required = false, + description = "parameter to repair the volume, leaks or all are the possible values") private String repair; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java index a4cd299dae9c..caa52bbd13b4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java @@ -98,7 +98,10 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme authorized = { RoleType.Admin }) private Boolean listSystemVms; - @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "State of the volume. Possible values are: Ready, Allocated, Destroy, Expunging, Expunged.") + @Parameter(name = ApiConstants.STATE, + type = CommandType.STRING, + allowedValues = {"Ready", "Allocated", "Destroy", "Expunging", "Expunged"}, + description = "State of the volume. Possible values are: Ready, Allocated, Destroy, Expunging, Expunged.") private String state; @Parameter(name = ApiConstants.IS_ENCRYPTED, type = CommandType.BOOLEAN, description = "list only volumes that are encrypted", since = "4.19.1", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java index 33a9251e094f..75a8ad324d2b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java @@ -50,9 +50,10 @@ public class UploadVolumeCmd extends BaseAsyncCmd implements UserCmd { ///////////////////////////////////////////////////// @Parameter(name = ApiConstants.FORMAT, - type = CommandType.STRING, - required = true, - description = "The format for the volume. Possible values include QCOW2, OVA, and VHD.") + type = CommandType.STRING, + allowedValues = {"QCOW2", "OVA", "VHD"}, + required = true, + description = "The format for the volume. Possible values include QCOW2, OVA, and VHD.") private String format; @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the volume") diff --git a/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java b/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java index fb1453e80ff6..a125475f6af9 100644 --- a/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java +++ b/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java @@ -54,6 +54,10 @@ public class ApiParameterResponse extends BaseResponse { @Param(description = "Comma separated related APIs to get the parameter") private String related; + @SerializedName("allowedvalues") + @Param(description = "List of allowed values for this parameter") + private List allowedValues; + private transient List authorizedRoleTypes = null; public ApiParameterResponse() { @@ -91,6 +95,14 @@ public void setRelated(String related) { this.related = related; } + public List getAllowedValues() { + return allowedValues; + } + + public void setAllowedValues(List allowedValues) { + this.allowedValues = allowedValues; + } + public void setAuthorizedRoleTypes(List authorizedRoleTypes) { this.authorizedRoleTypes = authorizedRoleTypes; } diff --git a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java index d412f12fce24..26a5beda0288 100644 --- a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java +++ b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java @@ -19,6 +19,7 @@ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -236,10 +237,34 @@ protected ApiDiscoveryResponse getCmdRequestMap(Class cmdClass, APICommand ap paramResponse.setSince(parameterAnnotation.since()); } paramResponse.setRelated(parameterAnnotation.entityType()[0].getName()); - if (parameterAnnotation.authorized() != null) { - paramResponse.setAuthorizedRoleTypes(Arrays.asList(parameterAnnotation.authorized())); + + Class allowedValueType = parameterAnnotation.allowedValueType(); + + if (allowedValueType != Enum.class) { + Enum[] enumConstants = allowedValueType.getEnumConstants(); + if (enumConstants != null) { + List allowedValues = Arrays.stream(enumConstants) + .map(Enum::toString) + .collect(Collectors.toList()); + + paramResponse.setAllowedValues( + Collections.unmodifiableList(allowedValues) + ); + } + } else { + String[] allowedValues = parameterAnnotation.allowedValues(); + if (allowedValues != null && allowedValues.length > 0) { + paramResponse.setAllowedValues( + Collections.unmodifiableList(Arrays.asList(allowedValues)) + ); + } } - response.addParam(paramResponse); + + if (parameterAnnotation.authorized() != null) { + paramResponse.setAuthorizedRoleTypes(Arrays.asList(parameterAnnotation.authorized())); + } + + response.addParam(paramResponse); } } return response; diff --git a/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImplTest.java b/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImplTest.java index e69b9523d449..c3761845ceef 100644 --- a/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImplTest.java +++ b/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImplTest.java @@ -54,6 +54,12 @@ public class ApiDiscoveryServiceImplTest { @InjectMocks ApiDiscoveryServiceImpl discoveryServiceSpy; + private enum TestAllowedValue { + FIRST, + SECOND, + THIRD + } + @Before public void setUp() { Mockito.when(apiCommandMock.name()).thenReturn("listApis"); @@ -107,6 +113,8 @@ public void getCmdRequestMapAddsParamsWithExposedAndIncludedInApiDocAnnotations( Mockito.when(parameterMock.name()).thenReturn("paramName"); Mockito.when(parameterMock.since()).thenReturn(""); Mockito.when(parameterMock.entityType()).thenReturn(new Class[]{Object.class}); + Mockito.doReturn(Enum.class).when(parameterMock).allowedValueType(); + Mockito.when(parameterMock.allowedValues()).thenReturn(new String[]{"VALUE1", "VALUE2"}); Mockito.when(parameterMock.description()).thenReturn("paramDescription"); Mockito.when(parameterMock.type()).thenReturn(BaseCmd.CommandType.STRING); Mockito.when(fieldMock.getAnnotation(Parameter.class)).thenReturn(parameterMock); @@ -118,6 +126,81 @@ public void getCmdRequestMapAddsParamsWithExposedAndIncludedInApiDocAnnotations( Assert.assertEquals(1, params.size()); ApiParameterResponse paramResponse = params.iterator().next(); Assert.assertEquals("paramName", ReflectionTestUtils.getField(paramResponse, "name")); + Assert.assertEquals( + Set.of("VALUE1", "VALUE2"), + Set.copyOf(paramResponse.getAllowedValues()) + ); + } + } + + @Test + public void getCmdRequestMapDoesNotSetAllowedValuesWhenNoneAreProvided() { + Field fieldMock = Mockito.mock(Field.class); + Parameter parameterMock = Mockito.mock(Parameter.class); + Mockito.when(parameterMock.expose()).thenReturn(true); + Mockito.when(parameterMock.includeInApiDoc()).thenReturn(true); + Mockito.when(parameterMock.name()).thenReturn("paramName"); + Mockito.when(parameterMock.since()).thenReturn(""); + Mockito.when(parameterMock.entityType()).thenReturn(new Class[]{Object.class}); + Mockito.doReturn(Enum.class).when(parameterMock).allowedValueType(); + Mockito.when(parameterMock.allowedValues()).thenReturn(new String[]{}); + Mockito.when(parameterMock.description()).thenReturn("paramDescription"); + Mockito.when(parameterMock.type()).thenReturn(BaseCmd.CommandType.STRING); + Mockito.when(fieldMock.getAnnotation(Parameter.class)).thenReturn(parameterMock); + + try (MockedStatic reflectUtilMockedStatic = Mockito.mockStatic(ReflectUtil.class)) { + reflectUtilMockedStatic.when(() -> + ReflectUtil.getAllFieldsForClass(any(Class.class), any(Class[].class))) + .thenReturn(Set.of(fieldMock)); + + ApiDiscoveryResponse response = + discoveryServiceSpy.getCmdRequestMap(ListApisCmd.class, apiCommandMock); + + Set params = response.getParams(); + Assert.assertEquals(1, params.size()); + + ApiParameterResponse paramResponse = params.iterator().next(); + + Assert.assertTrue( + paramResponse.getAllowedValues() == null + || paramResponse.getAllowedValues().isEmpty() + ); + } + } + + @Test + public void getCmdRequestMapGetsAllowedValuesFromEnum() { + Field fieldMock = Mockito.mock(Field.class); + Parameter parameterMock = Mockito.mock(Parameter.class); + + Mockito.when(parameterMock.expose()).thenReturn(true); + Mockito.when(parameterMock.includeInApiDoc()).thenReturn(true); + Mockito.when(parameterMock.name()).thenReturn("paramName"); + Mockito.when(parameterMock.since()).thenReturn(""); + Mockito.when(parameterMock.entityType()).thenReturn(new Class[]{Object.class}); + Mockito.doReturn(TestAllowedValue.class).when(parameterMock).allowedValueType(); + Mockito.when(parameterMock.description()).thenReturn("paramDescription"); + Mockito.when(parameterMock.type()).thenReturn(BaseCmd.CommandType.STRING); + + Mockito.when(fieldMock.getAnnotation(Parameter.class)).thenReturn(parameterMock); + + try (MockedStatic reflectUtilMockedStatic = Mockito.mockStatic(ReflectUtil.class)) { + reflectUtilMockedStatic.when(() -> + ReflectUtil.getAllFieldsForClass(any(Class.class), any(Class[].class))) + .thenReturn(Set.of(fieldMock)); + + ApiDiscoveryResponse response = + discoveryServiceSpy.getCmdRequestMap(ListApisCmd.class, apiCommandMock); + + Set params = response.getParams(); + Assert.assertEquals(1, params.size()); + + ApiParameterResponse paramResponse = params.iterator().next(); + + Assert.assertEquals( + Set.of("FIRST", "SECOND", "THIRD"), + Set.copyOf(paramResponse.getAllowedValues()) + ); } } } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java index 870b9b6df6e5..0d900a2ba1c0 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java @@ -64,7 +64,10 @@ public class QuotaSummaryCmd extends BaseListCmd { "Accounts which the caller has access. If domain ID is informed, this parameter is considered as true.") private Boolean listAll; - @Parameter(name = ApiConstants.ACCOUNT_STATE_TO_SHOW, type = CommandType.STRING, description = "Possible values are [ALL, ACTIVE, REMOVED]. ALL will list summaries for " + + @Parameter(name = ApiConstants.ACCOUNT_STATE_TO_SHOW, + type = CommandType.STRING, + allowedValues = {"ALL", "ACTIVE", "REMOVED"}, + description = "Possible values are [ALL, ACTIVE, REMOVED]. ALL will list summaries for " + "active and removed accounts; ACTIVE will list summaries only for active accounts; REMOVED will list summaries only for removed accounts. The default value is ACTIVE.", since = "4.23.0") private String accountStateToShow; diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java index 775b6c6f6db1..0d75670afb7a 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java @@ -88,7 +88,9 @@ public class AddKubernetesSupportedVersionCmd extends BaseCmd implements AdminCm description = "If set to true the Kubernetes supported version ISO will bypass Secondary Storage and be downloaded to Primary Storage on deployment. Default is false") private Boolean directDownload; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "the CPU arch of the Kubernetes ISO. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/version/ListKubernetesSupportedVersionsCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/version/ListKubernetesSupportedVersionsCmd.java index b50d2e6ada32..d4ed5e7ea753 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/version/ListKubernetesSupportedVersionsCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/version/ListKubernetesSupportedVersionsCmd.java @@ -66,7 +66,9 @@ public class ListKubernetesSupportedVersionsCmd extends BaseListCmd { description = "The ID of the minimum Kubernetes supported version") private Long minimumKubernetesVersionId; - @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + allowedValues = {"x86_64", "aarch64", "s390x"}, description = "the CPU arch of the binaries ISO. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; diff --git a/scripts/check_allowed_values.py b/scripts/check_allowed_values.py new file mode 100644 index 000000000000..9cb6b72316b7 --- /dev/null +++ b/scripts/check_allowed_values.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 + +import re +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent + + +def find_java_files(): + return ROOT.glob("**/*.java") + + +def extract_parameter_blocks(source): + blocks = [] + start = 0 + + while True: + match = source.find("@Parameter(", start) + if match == -1: + break + + depth = 0 + in_string = False + escape = False + end = None + + for i in range(match, len(source)): + char = source[i] + + if in_string: + if escape: + escape = False + elif char == "\\": + escape = True + elif char == '"': + in_string = False + continue + + if char == '"': + in_string = True + elif char == "(": + depth += 1 + elif char == ")": + depth -= 1 + if depth == 0: + end = i + 1 + break + + if end is None: + break + + blocks.append((match, source[match:end])) + start = end + + return blocks + + +def is_string_parameter(block): + return bool( + re.search( + r"\btype\s*=\s*CommandType\.STRING\b", + block, + ) + ) + + +def extract_description(block): + match = re.search( + r"\bdescription\s*=\s*((?:\"(?:\\.|[^\"\\])*\")" + r"(?:\s*\+\s*\"(?:\\.|[^\"\\])*\")*)", + block, + re.DOTALL, + ) + + if not match: + return "" + + expression = match.group(1) + + strings = re.findall( + r'"((?:\\.|[^"\\])*)"', + expression, + re.DOTALL, + ) + + return "".join(strings) + + +def extract_valid_values(description): + patterns = [ + r"\bvalid\s+values?\s*(?:are|:)\s*(.+?)(?:\.|$)", + r"\bvalid\s+options?\s*(?:are|:)\s*(.+?)(?:\.|$)", + r"\bpossible\s+values?\s*(?:are|:|include)\s*(.+?)(?:\.|$)", + r"\bpossible\s+options?\s*(?:are|:|include)\s*(.+?)(?:\.|$)", + r"\ballowed\s+values?\s*(?:are|:)\s*(.+?)(?:\.|$)", + ] + + match = None + + for pattern in patterns: + match = re.search(pattern, description, re.IGNORECASE) + if match: + break + + if not match: + return [] + + values_text = match.group(1).strip() + + # Some descriptions contain a fixed set of examples but also + # explicitly allow additional values. These are not closed enums + # and therefore should not require an allowedValues annotation. + if re.search(r"\bor\s+valid\s+protocol\s+number\b", values_text, re.IGNORECASE): + return [] + + # Remove a leading colon if the wording leaves one behind. + values_text = values_text.lstrip(":").strip() + + # Convert "A, B, and C" into "A, B, C". + values_text = re.sub( + r",?\s+and\s+", + ", ", + values_text, + flags=re.IGNORECASE, + ) + + values = [ + value.strip().strip('"').strip("'") + for value in values_text.split(",") + ] + + return [value for value in values if value] + + +def extract_parameter_name(block): + match = re.search( + r"\bname\s*=\s*(?:ApiConstants\.([A-Z0-9_]+)|\"([^\"]+)\")", + block, + ) + + if not match: + return "" + + if match.group(1): + return f"ApiConstants.{match.group(1)}" + + return match.group(2) + +def extract_inner_enums(source): + enums = {} + + pattern = re.compile( + r"\benum\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([^}]*)\}", + re.DOTALL, + ) + + for match in pattern.finditer(source): + enum_name = match.group(1) + body = match.group(2) + + constants = [] + + for constant in body.split(","): + constant = constant.strip() + + constant_match = re.match( + r"([A-Z][A-Z0-9_]*)\b", + constant, + ) + + if constant_match: + constants.append(constant_match.group(1)) + + if constants: + enums[enum_name] = constants + + return enums + + +def extract_parameter_field_name(source, parameter_end): + match = re.match( + r"\s*private\s+String\s+([A-Za-z_][A-Za-z0-9_]*)\s*;", + source[parameter_end:], + ) + + if not match: + return None + + return match.group(1) + + +def find_enum_for_parameter(source, parameter_end, field_name): + if not field_name: + return None + + enum_pattern = re.compile( + rf"\benum\s+{re.escape(field_name.capitalize())}Values\b" + r"\s*\{", + ) + + if enum_pattern.search(source): + return field_name.capitalize() + "Values" + + return None + +def check_file(path): + source = Path(path).read_text(encoding="utf-8") + violations = [] + + enums = extract_inner_enums(source) + + for start, block in extract_parameter_blocks(source): + if not is_string_parameter(block): + continue + + if re.search(r"\ballowedValues\s*=", block): + continue + + if re.search(r"\ballowedValueType\s*=", block): + continue + + parameter_end = start + len(block) + field_name = extract_parameter_field_name( + source, + parameter_end, + ) + + enum_name = find_enum_for_parameter( + source, + parameter_end, + field_name, + ) + + if enum_name and enum_name in enums: + line = source[:start].count("\n") + 1 + name = extract_parameter_name(block) + + violations.append( + ( + str(path), + line, + enums[enum_name], + name, + ) + ) + + continue + + description = extract_description(block) + + if not description: + continue + + values = extract_valid_values(description) + + if not values: + continue + + line = source[:start].count("\n") + 1 + name = extract_parameter_name(block) + + violations.append( + (str(path), line, values, name) + ) + + return violations + +def main(): + violations = [] + + for path in find_java_files(): + violations.extend(check_file(path)) + + if violations: + print("Missing allowedValues annotations:") + + for path, line, values, name in violations: + print( + f"{path}:{line}: " + f"parameter '{name}' specifies valid values " + f"{values} but has no allowedValues annotation" + ) + + return 1 + + print("No missing allowedValues annotations found.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/test_check_allowed_values.py b/scripts/test_check_allowed_values.py new file mode 100644 index 000000000000..c7e1d6be280c --- /dev/null +++ b/scripts/test_check_allowed_values.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 + +import tempfile +import unittest +from pathlib import Path + +from scripts import check_allowed_values + + +class CheckAllowedValuesTest(unittest.TestCase): + + def test_extract_valid_values(self): + description = ( + "Provisioning type used to create volumes. " + "Valid values are thin, sparse, fat." + ) + + self.assertEqual( + check_allowed_values.extract_valid_values(description), + ["thin", "sparse", "fat"], + ) + + def test_extract_valid_values_with_and(self): + description = ( + "Valid values are HOURLY, DAILY, WEEKLY, and MONTHLY" + ) + + self.assertEqual( + check_allowed_values.extract_valid_values(description), + ["HOURLY", "DAILY", "WEEKLY", "MONTHLY"], + ) + + def test_open_ended_protocol_values_are_ignored(self): + description = ( + "TCP/UDP/ICMP/ALL or valid protocol number" + ) + + self.assertEqual( + check_allowed_values.extract_valid_values(description), + [], + ) + + def test_open_ended_protocol_values_with_protocol_reference_are_ignored(self): + description = ( + "TCP/UDP/ICMP/ALL or valid protocol number " + "(see /etc/protocols)" + ) + + self.assertEqual( + check_allowed_values.extract_valid_values(description), + [], + ) + + def test_string_parameter_is_detected(self): + block = """ + @Parameter( + name = "test", + type = CommandType.STRING, + description = "Valid values are A, B" + ) + """ + + self.assertTrue( + check_allowed_values.is_string_parameter(block) + ) + + def test_non_string_parameter_is_ignored(self): + block = """ + @Parameter( + name = "test", + type = CommandType.INTEGER, + description = "Valid values are 1, 2" + ) + """ + + self.assertFalse( + check_allowed_values.is_string_parameter(block) + ) + + def test_parameter_with_allowed_values_is_not_reported(self): + source = """ + @Parameter( + name = "test", + type = CommandType.STRING, + allowedValues = {"A", "B"}, + description = "Valid values are A, B" + ) + private String test; + """ + + with tempfile.NamedTemporaryFile( + mode="w", + suffix=".java", + encoding="utf-8", + delete=False, + ) as file: + file.write(source) + path = Path(file.name) + + try: + self.assertEqual( + check_allowed_values.check_file(path), + [], + ) + finally: + path.unlink() + + def test_missing_allowed_values_is_reported(self): + source = """ + @Parameter( + name = "test", + type = CommandType.STRING, + description = "Valid values are A, B" + ) + private String test; + """ + + with tempfile.NamedTemporaryFile( + mode="w", + suffix=".java", + encoding="utf-8", + delete=False, + ) as file: + file.write(source) + path = Path(file.name) + + try: + violations = check_allowed_values.check_file(path) + + self.assertEqual(len(violations), 1) + self.assertEqual( + violations[0][2], + ["A", "B"], + ) + finally: + path.unlink() + + def test_non_string_parameter_is_not_reported(self): + source = """ + @Parameter( + name = "test", + type = CommandType.INTEGER, + description = "Valid values are 1, 2" + ) + private Integer test; + """ + + with tempfile.NamedTemporaryFile( + mode="w", + suffix=".java", + encoding="utf-8", + delete=False, + ) as file: + file.write(source) + path = Path(file.name) + + try: + self.assertEqual( + check_allowed_values.check_file(path), + [], + ) + finally: + path.unlink() + + def test_inner_enum_is_extracted(self): + source = """ + public class TestCmd { + public enum RepairValues { + LEAKS, ALL + } + } + """ + + enums = check_allowed_values.extract_inner_enums(source) + + self.assertEqual( + enums, + { + "RepairValues": ["LEAKS", "ALL"], + }, + ) + + def test_enum_parameter_without_allowed_values_is_reported(self): + source = """ + public class TestCmd { + @Parameter( + name = "repair", + type = CommandType.STRING + ) + private String repair; + + public enum RepairValues { + LEAKS, ALL + } + } + """ + + with tempfile.NamedTemporaryFile( + mode="w", + suffix=".java", + encoding="utf-8", + delete=False, + ) as file: + file.write(source) + path = Path(file.name) + + try: + violations = check_allowed_values.check_file(path) + + self.assertEqual(len(violations), 1) + self.assertEqual( + violations[0][2], + ["LEAKS", "ALL"], + ) + finally: + path.unlink() + + def test_enum_parameter_with_allowed_values_is_not_reported(self): + source = """ + public class TestCmd { + @Parameter( + name = "repair", + type = CommandType.STRING, + allowedValues = {"LEAKS", "ALL"} + ) + private String repair; + + public enum RepairValues { + LEAKS, ALL + } + } + """ + + with tempfile.NamedTemporaryFile( + mode="w", + suffix=".java", + encoding="utf-8", + delete=False, + ) as file: + file.write(source) + path = Path(file.name) + + try: + self.assertEqual( + check_allowed_values.check_file(path), + [], + ) + finally: + path.unlink() + + def test_enum_parameter_with_allowed_value_type_is_not_reported(self): + source = """ + public class TestCmd { + @Parameter( + name = "repair", + type = CommandType.STRING, + allowedValueType = RepairValues.class + ) + private String repair; + + public enum RepairValues { + LEAKS, ALL + } + } + """ + + with tempfile.NamedTemporaryFile( + mode="w", + suffix=".java", + encoding="utf-8", + delete=False, + ) as file: + file.write(source) + path = Path(file.name) + + try: + self.assertEqual( + check_allowed_values.check_file(path), + [], + ) + finally: + path.unlink() + + +if __name__ == "__main__": + unittest.main()